Adding stuff (e.g. Noise instances) to an abstract class

This is a question about best practices for code organisation. What’s a good pattern for complex Thing/Actor classes which have a bunch of “parts” attached to them. Here’s a toy example to illustrate:

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

class Blob: Thing
        vocabWords = 'blob'
        location = startRoom
        desc() {
                "It looks like your average blob. ";
        }
        initializeThing() {
                inherited;
                new Pseudopod(self);
                new BlobWibble(self);
                new BlobWobble(self);
                new BlobBibble(self);
                new BlobBobble(self);
        }
;

class Pseudopod: Component 'pseudopod' 'pseudopod'
        "It's looks exactly like every other pseudopod you've ever seen. "
        construct(obj) {
                inherited;
                location = obj;
        }
;
class BlobNoise: Noise
        isAmbient = true
        descWithSource() { "<<self.blobNoise>> "; }
        hereWithSource() { "<<self.blobNoise>> "; }
        construct(obj) {
                inherited;
                location = obj;
        }
;

class BlobWibble: BlobNoise 'wibble' 'wibble'
        blobNoise = 'The blob goes wibble. '
;
class BlobWobble: BlobNoise 'wobble' 'wobble'
        blobNoise = 'The blob goes wobble. '
;
class BlobBibble: BlobNoise 'bibble' 'bibble'
        blobNoise = 'The blob goes bibble. '
;
class BlobBobble: BlobNoise 'bobble' 'bobble'
        blobNoise = 'The blob goes bobble. '
;

theBlob: Blob 'blob' 'blob'
        location = startRoom
;

startRoom:      Room 'Void'
        "This is a featureless void. "
;

me:     Actor
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

This creates an abstract Blob class. Each Blob gets one Pseudopod instance and four Noise instances.

Clearly this is a nonsensical example, and in this “game” there’s only one Blob that gets instanced, theBlob, so it would be easy to just throw the “stuff” onto it using the canonical + Noise 'bibble' 'bibble'; format. But if I eventually want to create a bunch of different Blob instances it’s convenient if this sort of thing is baked into the parent class. Basically I don’t want to have to manually add all of the Noise and Component instances that are common to all Blobs every time I create an new Blob instance.

The code as written more or less works, but I’ve had to twiddle the Blob class’ initializeThing(), as well as over-writing the Component/Noise bits’ construct() methods. Which feels a bit clumsy and brittle (in the sense that it’s likely to break if anything in the library changes).

Am I missing something in the library or some clever design pattern that could do this more cleanly?

Could your initializeThing() method look like this:

initializeThing() {
  inherited;
  moveInto(new Pseudopod);
  moveInto(new BlobWibble);
  // ... etc. ...
}

Then your classes don’t need their construct methods to set up their location. Since initializeThing() is called at pre-init time, everything should be set up when the game starts.

Note that this only works if all your Blob objects are declared as instances in code. If you’re creating them dynamically (from, say, a BlobFactory object), then you’ll need a construct() or some other mechanism to build the relationships.

Yeah, that’s good (although you’ve got the syntax backwards…they want to be new Pseudopod().moveInto(self) or the equivalent).

I’d considered doing a factory or decorator sort of pattern for this, but I really don’t have a good feel for how either would interact with Adv3 and/or other common TADS3 libraries.

Does TADS3 provide a mechanism via which you can instantiate an object with the class name given by a variable/argument/whatever? That would make this sort of thing much more straightforward.

Ah, right. That’s what I get for answering off-the-cuff.

For class name strings, you might need to use the macro processor and some stringizing.

TadsObject.createInstance() and TadsObject.createInstanceOf() can be used if you have the class object handy.

I’ve not gone deep with TADS reflection and dynamic creation, so there might be a better way.

Ah, cool. Irritatingly the documentation for Thing doesn’t list the methods inherited from TadsObject so I missed createInstance().

So currently I’m thinking of something like this:

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

DefineLiteralAction(Spawn)
        execAction() {
                local id, obj;

                id = getLiteral();
                if(blobStuff.blobClass[id] == nil) {
                        "Nothing happens. ";
                        exit;
                }
                obj = blobStuff.blobClass[id].createInstance();
                obj.moveInto(startRoom);
                "Poof, have <<obj.aName>>. ";
        }
;
VerbRule(Spawn) 'spawn' singleLiteral : SpawnAction
        verbPhrase = 'spawn/spawning (what)';

blobStuff: object
        blobClass = [
                'red' -> RedBlob,
                'bigRed' -> RedBlob,
                'blue' -> BlueBlob,
                'common' -> Blob
        ]
        blobContents = [
                'red' -> [ SmallPseudopod, BlobNoiseQuiet ],
                'bigRed' -> [ SmallPseudopod, LargePseudopod, BlobNoiseLoud ],
                'blue' -> [ SmallPseudopod, BlobNoiseQuiet ],
                'common' -> [ Pseudopod, BlobNoise ]
        ]
;

class Blob: Thing
        vocabWords = 'blob'
        name = 'blob'
        blobID = 'common'
        desc() { "It looks like your average blob. "; }
        initializeThing() {
                local cfg;

                if((self.blobID == nil)
                        || (blobStuff.blobContents[self.blobID] == nil))
                        return;

                cfg = blobStuff.blobContents[self.blobID];

                cfg.forEach(function(o) {
                        local n;
                        n = o.createInstance();
                        n.moveInto(self);
                });
        }
;
class RedBlob: Blob 'red blob' 'red blob'
        "It's a red blob. "
        blobID = 'red'
;
class BlueBlob: Blob 'blue blob' 'blue blob'
        "It's a blue blob. "
        blobID = 'blue'
;

class Pseudopod: Component 'pseudopod' 'pseudopod'
        "It's looks exactly like every other pseudopod you've ever seen. "
;
class LargePseudopod: Pseudopod 'large pseudopod' 'large pseudopod'
        "It's large. "
;
class SmallPseudopod: Pseudopod 'small pseudopod' 'small pseudopod'
        "It's small. "
;

class BlobNoise: SimpleNoise 'blob noise/noises' 'noise'
        "It sounds abstract. "
        isAmbient = true
;
class BlobNoiseLoud: BlobNoise 'loud blob noise/noises' 'noise'
        "It's loud. "
;
class BlobNoiseQuiet: BlobNoise 'quiet blob noise/noises' 'noise'
        "It's quiet. "
;

startRoom:      Room 'Void'
        "This is a featureless void. "
;

me:     Actor
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

This defines a “spawn” verb that creates new Blob instances. It takes an argument, so >spawn red creates a red blob, >spawn blue spawns a blue blob, and so on. This depends on the blob types being defined in the blobStuff object, which defines which base class and stuff (components, sense objects, and so on) to add to the instance.

This is clearly overkill for what it’s doing in this example–it would be easier to just add a soundDesc() to the class(es), and so on. But think of a situation where the player has a car, and they can trade the car they have for other models, and modify/customise whichever model they have at the moment. So you have a complex object whose behaviour in-game is dependent on a whole bundle of individual parts: on a '57 Chevy Bel Air there should be a rumbling sound when it’s running, and smoke from the tailpipe, and so on…and all of those things should provide appropriate opportunities for player interaction (smelling the exhaust, listening to the engine, and so on). On the other hand if the car is a 2017 Tesla Model 3, the motor is silent and there’s no exhaust.

Most code example games seem to presume you’re going to hardcode everything…if there’s a '57 Bel Air, there’s exactly one of them and all of its properties are baked into the object definitions in the source. Alternately, if there are two different Bel Airs then they’d both be statically defined, or they’d be implemented via a tangled mess of conditional statements scattered through a general Bel Air class.

It would certainly be possible to sorta brute force the sort of solution that I’m looking for by a lot of procedural jiggery-pokery to more or less hand-build each instance. But I’d really like to be able to something more or less like it’s done in the example above: define the bits and pieces individually and then define a config object (like blobStuff in the example) that defines the relationships. Since you can’t just do something like:

class Blob:  Thing
;
+ class Pseudopod: Component 'pseudopod' 'pseudopod'
     "It's a generic pseudopod. "
;

…to associate bits and pieces with classes.

Also: what is the syntax, if any, for declaring an implicit object in an implicit object? Like in the blobStuff object in the example I’d much rather do something like:

blobStuff: [
     'red' -> {
          baseClass:  RedBlob,
          partList: [ SmallPseudopod, BlobNoiseQuiet ]
     }
]

…and so on, but I can’t figure out what syntax for this, if any, the compiler will accept.

Maybe this helps:

https://gist.github.com/realnc/a108e72b827d9e405a899d67b2484222

That’s an example of an approach to the kind of problem I’m talking about, but unless I’m missing something it doesn’t have any of the features I’m looking for in a solution. Basically it just packs all of the assembly logic into a hardcoded initializeThing() method.

That’s not a terrible approach, but it really seems to be more of an approach to filling in defaults–if one Actor has a nose, make sure that all Actors have noses–instead of templating a bunch of different derived classes that all get different parts.

I imagine most games implemented using that library have Actors that either have a default nose (for example) that’s just there for consistency, or a unique nose that’s declared along with the Actor instance that owns it. What I’m interested in doing is (more or less) implement a bunch of different abstract nose classes, each of which has a bunch of individual components (objects, sense info, and so on) that need to be inherited by instances.

It’s certainly possible to do this by just writing a different initializeThing() for each of the classes that’s an elaborate mass of conditionals and hard-coded moveInto(self) actions. But that seems ugly and difficult to maintain. It also seems like an odd procedural hack in what is ostensibly an object oriented environment.

I think you’re asking about the nested object syntax:

blobStuff: Thing
  red: RedBlob {
    partList: [ SmallBlob, QuietBlob ]
  }
;

Note the : (colon) after the property name. Also, you must use the braces.

Not quite. Or at least I don’t think so. I want to be able to do something like in the “spawn” command in my previous example, which involves getting an element by name/id. Something like

local id = 'red';
local cfg = blobStuff[id]

…but that’ll throw a “this type of value cannot be indexed” error at runtime if blobStuff isn’t of type LookupTable. But I have no idea how to declare a LookupTable whose values are objects.

Or, alternately, other way to get/access a property on an object given the property name.

Perhaps you’re looking for something like this:

DefineLiteralAction(Spawn) 
  blobStuff = static new LookupTable([ 'red', RedBlob, 'blue', BlueBob ])
;

… where the list initializes the table with alternating key/values.

There’s a shorthand for this, fortunately:

DefineLiteralAction(Spawn) 
  blobStuff = [ 'red' -> RedBlob, 'blue' -> BlueBlob ]
;

Sorry, I’m having trouble being clear about what I’m trying to accomplish. I understand implicitly declaring LookupTables; that’s how blobStuff is declared in the example I posted. But in it, there are two LookupTables, one for the base class and one for the parts list.

What I’d like to be able to do is declare, directly, a LookupTable whose values are objects that contain both bits of information. This more or less accomplishes it, only in parts:

redCfg: object
        baseClass = RedBlob
        partList = [ SmallPseudopod, BlobNoiseQuiet ]
;
blueCfg: object
        baseClass = BlueBlob,
        partList = [ SmallPseudopod, BlobNoiseQuiet ]
;
blobCfg: object
        hash = [
                'red' -> redCfg,
                'blue' -> blueCfg
        ]
;

So having done that I can do something like

local id = 'red';
local obj = blobCfg.hash[id].baseClass.createInstance();

I’d just like to be able to do that in a single declaration, rather than declaring each of the objects separately and then declaring a LookupTable containing the already-created objects. If that makes more sense.

I’d also like to be able to do this in the global scope without having to wrap the LookupTable in a containing object. So instead of accessing elements of the table via (to use the example I just gave earlier in this post) blobCfg.hash[id], just blobCfg[id]. But while

tableName = [
     'foo' -> someObject,
     'bar' -> otherObject
]

is valid inside a object declaration, it isn’t in the global scope. So

// this doesn't work
tableName = [
   'foo' -> someObject
]

and

// this also doesn't work
tableName:  LookupTable
     'foo' -> someObject
;

…don’t work, but look kinda what I’d like to do.

If that all makes more sense.

Ok, hopefully I’m grokking what you’re asking about.

I believe the inline object syntax can do what you’re asking:

class Blob: Thing
  hash = [
    'red' -> object { baseClass = RedBlob; partList = [ 'a', 'b' ]; },
    'blue' -> object { baseClass = BlueBlob; partList = [ 'c', 'd' ]; }
  ]
;

"<<Blob.hash['red'].partList[1]>>" prints 'a'.

It’s my understanding that can’t be done. What you can do (and what the library does) is #define a name to reference a property within a class or object:

#define gPlayerChar (libGlobal.playerChar)

That said, I wonder if something like this suits your needs:

DefineLiteralAction(Spawn)
  execAction() {
    local id = getLiteral();

    local idx = Blob.subclasses.indexWhich({ bc: bc.blobID == id });
    if (!idx) {
      "Nothing happens.  ";
      exit;
    }

    local obj = Blob.subclasses[idx].createInstance();
    obj.moveInto(kitchen);

    "Poof, have <<obj.aName>>. ";
  }
;

class Blob: Thing
  vocabWords = 'blob'

  subclasses = [
    RedBlob,
    BigRedBlob,
    BlueBlob,
    CommonBlob
  ]

  blobID = nil
  components = []

  initializeThing() {
    foreach (local componentClass in components)
      componentClass.createInstance().moveInto(self);
  }
;

class CommonBlob: Blob 'common blob' 'common blob'
  "It's a common blob. "
  blobID = 'common'
  components = [ Pseudopod, BlobNoise ]
;

class RedBlob: Blob 'red blob' 'red blob'
  "It's a red blob. "
  blobID = 'red'
  components = [ SmallPseudopod, BlobNoiseQuiet ]
;
...

This associates the “recipes” for the components in the classes themselves, as well as anything else SPAWN may need for its purposes. The class list is held in Blob rather than a separate object, but that’s more a matter of taste.

What I haven’t been able to find is a way of iterating over all subclasses of Blob via reflection. That could make the lookup truly dynamic.

But, since most games are closed systems, it’s probably fine to have a master list of Blob subclasses that can be iterated over.

1 Like

I tried using some inline objects in my game and then abandoned them because it seemed like errors were sometimes thrown that didn’t seem to have anything to do with my code…

Thanks, that’s very nearly what I want. I’m actually looking at something like

blobCfg: object
        hash = [
                'red' -> object {
                        baseClass = RedBlob
                        partList = [ SmallPseudopod, BlobNoiseQuiet ]
                },
                'blue' -> object {
                        baseClass = BlueBlob,
                        partList = [ SmallPseudopod, BlobNoiseQuiet ]
                }
        ]
;

…and was being confounded by the fact that there are two discrete syntaxes needed to declare the objects in this case:

objName: object
     propName = value
;

…for the declaration in the global namespace, and:

object {
     propName = value
}

…inside the enclosing object. This is somewhat…counterintuitive. Anyway, thanks for taking the time to work out what I was trying to ask for and helping.

Yeah, part of the complication is because I’m trying to build major components as more or less standalone libraries instead of hardcoded special cases for a specific game. And so I’m trying to implement, for example, an abstract Automobile class that takes care of managing all of the individual car parts, their sense data, special behaviours, new verbs, and so on.

And then in the game itself the different kinds of cars that can appear in the game are enumerated in separate configuration object that’s not part of the library. This is used for the “recipes” that are used to create individual instances, which can then themselves be customized as a part of gameplay. If that all makes sense.

I’m leaning toward putting an abstract factory class in the library that the game is required to create an instance of that contains all the “recipes” used in the game. I suppose it could also work to require the game to create its own subclass of Automobile, MyAuto or whatever, that contains the “recipes”, and then individual cars in the game would be instances of MyAuto. But that feels less intuitive to me.

Quick note, you can use both object syntaxes in your source code at the top namespace level. It’s only within an object you’re restricted to the braces style (because the no-brace style is ambiguous in that context).

The braces style also permits semicolons between properties, which is handy if you’re like me and add semis due to C-style-programming muscle memory.

My memory is shaky, but I believe the no-brace style comes from TADS 2, and the braces style was added to TADS 3.

Ah, cool. I must’ve been remembering some problem with declaring a LookupTable, which is what I really wanted to do.

Speaking of inline declarations, is there a valid syntax for declaring an EventList which has another EventList as an element? For example, a StopEventList whose last element is a RandomEventList. Here’s a working example that illustrates the behaviour:

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

DefineIAction(Twiddle)
        doubleList = nil

        execAction() {
                local i, l;

                l = [ 'Stop 1. ', 'Stop 2. ', 'Stop 3. ' ];
                l = l.append(new RandomEventList([
                        'Rand 1. ', 'Rand 2. ', 'Rand 3. '
                ]));
                self.doubleList = new StopEventList(l);

                for(i = 0; i < 8; i++) {
                        "<<doubleList.doScript>> ";
                }
        }
;
VerbRule(Twiddle) 'twiddle' : TwiddleAction verbPhrase = 'twiddle/twiddling';

startRoom:      Room 'Void'
        "This is a featureless void. "
;

me:     Actor
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

This defines a verb “twiddle” that just outputs the elements of a StopEventList that lives in doubleList. The first three elements of the array used to initialize the StopEventList are strings and the last element is a RandomEventList.

The resulting behaviour is that it outputs each of the string members of the array, and then picks random elements from the RandomEventList. Which is exactly what I want.

But instead of declaring it the way it is in the example, I’d like to be able to do something like:

// this doesn't work
doubleList: StopEventList { [
     'Stop 1. ',
     'Stop 2. ',
     'Stop 3. ',
     RandomEventList { [
          'Rand 1. ',
          'Rand 2. ',
          'Rand 3. '
     ] }
] }

…but this is not valid syntax and I can’t figure out what, if any, the valid syntax is.

Edit: And I’m aware that the behaviour described can be accomplished using a ShuffledEventList by specifying a list of firstEvents. I’m interested in understanding the inline syntax, not just looking for a way to accomplish this specific effect.

1 Like

I got it to work doing it this way:

  events: StopEventList { [
    'Stop 1.',
    'Stop 2.',
    'Stop 3.',
    new RandomEventList([
      'Random 1.',
      'Random 2.',
      'Random 3.'
    ])
  ] }

…but I couldn’t find a general inline syntax for adding objects to a declared list without using new. This only works because EventList accepts the list in its constructor.

2 Likes

Outstanding, thanks.

Like I said (and I’ll repeat for anyone who finds this via search engine) that:

events:  ShuffledEventList {
     [ 'Stop 1. ', 'Stop 2. ', 'Stop 3' ]
     [ 'Random 1. ', 'Random 2. ', 'Random 3' ] 
}

…is a more concise way to achieve this specific behaviour. I’m implementing a couple game-specific EventList types and wanted to know the general syntax.