Is there a way to define new "switch routines"?

Hello!

It wouldn’t surprise me if this is covered somewhere, but I haven’t been able to find the right search terms. I’m most familiar with the parts of Inform described in the DM4, up to around version 6.31.

Is there a way to write a new routine, ideally as an object property, that is implicitly the body to a switch statement? As far as I can tell, this is how routines like before, after, and life work, and I would like to be able to make my own.

In case there’s a clearly better way to do it, my motivation is to write state machines for classes. As a simple example, for objects that need to behave differently at different times of day:

!!! Time of day
Constant DAYTIME = 0;
Constant NIGHTTIME = 1;

Global time_of_day = DAYTIME;

Class Day_Sensitive_Object
  with time_behavior [;
    DAYTIME:
      rfalse;
    NIGHTTIME:
      rfalse;
    ],
    each_turn [;
      switch (time_of_day) {
      ! Somehow invoke self.time_behavior() here
      }
    ];

Day_Sensitive_Object nightlight "nightlight",
  with time_behavior [;
    DAYTIME:
      self has ~light;
    NIGHTTIME:
      self has light;
    ];

So, is this possible? Or is it too built into the compiler, and not something available to a normal author?

Thanks in advance!

I think this is something approaching what you want:

!!! Time of day
Constant DAYTIME = 0;
Constant NIGHTTIME = 1;

Global time_of_day = DAYTIME;

Class Day_Sensitive_Object
  with time_behavior [;
         rfalse;
	   ],
	each_turn [;
	   self.time_behavior();
	];

Day_Sensitive_Object nightlight "nightlight"
  with name 'nightlight',
	   time_behavior [;
	      switch (time_of_day) {
	        DAYTIME:
	          give self ~light;
	        NIGHTTIME:
	          give self light;
	      }
	      if (self has light) "The nightlight glows softly."; ! optional
	    ];

The syntax that you’re trying to use for DAYTIME and NIGHTTIME is reserved for action names. (That’s why the compiler complains about the lack of a DAYTIMESub routine.) You can use a regular switch statement, however.

Ah, I hadn’t realized that only actions were used that way. That makes sense.

Having a routine with just a switch statement is probably good enough. I’m kind of trying to maximize the encapsulation to force me to think about some parts of the code flow as simply as possible (for example, I don’t think I want to have the option of that if statement at the bottom; I don’t trust myself with it, and will tend to overcomplicate my logic), so if there were a way to write it closer to how I did, I’d want to use it.

Thanks for the quick answer!

I believe Inform uses the variable sw__var for the implicit routine switch. So I think you could push it, set it to another value, call your routine then restore the variable by popping it.

each_turn [;
    @push sw__var;
    sw__var = time_of_day;
    self.time_behavior();
    @pop sw__var;
];

I haven’t tested it, and I’m pretty sure it won’t work as written anyway because Inform expects that the variable is an action name, but I guess it’s still a fun thing to know, and it could be useful in other cases.

Actually, if you really want this syntax, you could define a fake action with Fake_action DAYTIME instead of a constant. So it can only work with enumeration-like things, and not, say, numbers or objects. (Again, to be tested, I might be mistaken somewhere.)

(Although it’s kind of a “secret” feature, so other people reading your source could be confused.)

It may suffice to just define a different property for each state:

Day_Sensitive_Object nightlight "nightlight",
  with time_daytime [;
      give self ~light;
  ],
  with time_nighttime:
      give self light;
  ];

This would get messy if you have lots of states, but for simple situations it’s easy.

Ooh, thanks! I’ll play with this.

I’m pretty of willing to use unorthodox syntax, as long as I can find ways to document it well enough that the “other person” that is me a year in the future will be able to figure it out again, if I get a significant simplification in the main game logic out of it (though it’s not clear whether this will meet that bar.)

Yeah, one of my concerns is wanting to be able to support potentially large numbers of states, and to make it easy to add new ones. I suppose that any new state could have its state property added to the class with a default value and then only the objects or subclasses that need to have specific behavior for the state will override it.

I am assuming that the “dispatching” here would be a routine on Day_Sensitive_Object like:

run_time_state [;
  switch(time_of_day) {
  DAYTIME: self.time_daytime();
  NIGHTTIME: self.time_nighttime();
  }
]

and then I’d just call the routine from each_turn or whatever. That might not be too bad. I’ll give it a try, too, and see how it feels. Thank you!

Oops – I pasted the optional line to the wrong place, anyway… it ended up as part of the NIGHTTIME block inside the switch statement when it was supposed to be outside the switch statement. (Now fixed above.)

Thinking a bit more about that sw__var thing, it might be even more complicated to use, because Inform sets it while calling a method with self.time_behavior(), so setting it ourselves before the call won’t matter.

You’d have to replace the veneer routine CA__Pr to make it set sw__var to your time_of_day variable instead of the current action if the local a (or maybe it’s another local, not sure) of that routine is your special method (or use some other condition). The veneer routine is defined in this file ; just copy and edit the contents of CA__Pr in your project after using a Replace directive.

Again, I’m far from being an expert so I may be wrong.

And again, maybe it’s just simpler to use a regular switch statement. But at least now you have an answer on how to define a new “switch routine”! :slightly_smiling_face:

If you make sure you always call the property routine using a special routine, you can modify sw__var directly, e.g.

[ CallMyProp obj prop switchvalue oldself oldsw i len v ret;
  oldself = self;
  oldsw = sw__var;
  self = obj;
  sw__var = switchvalue;
  if(obj provides prop) {
    len = obj.#prop / 2;
    for(i=0: i<len: i++) {
      v = obj.&prop-->i;
      if(v ~= NULL or 0) {
        ret = indirect(v);
        if(ret) break;
      }
    }
  }
  self = oldself;
  sw__var = oldsw;
  return ret;
];

This is untested code. It should work with additive properties as well. A property value of NULL or 0 is the same as giving a routine that just returns false.

1 Like