Why can't rulebooks be more dynamic, again?

I’ve a feeling this is mentioned somewhere already, but: is there a reason why rules can’t be changed, reordered, removed from rulebooks and so on with “now”?

This would allow for all sorts of intriguing possibilities about changing the makeup of in-game systems due to player action, such as the command >OVERTURN PROP 8 triggering the phrase “now the can’t marry people of your own gender rule is not listed in the check marrying rulebook.”

Congratulations! You’ve just described procedural rules with a nicer syntax! Due to speed issues with Parchment et al, your feature will come on down. ::plays The Price Is Right theme::

I once tried designing some gameplay about the passing & repealing of laws in that way, but found it can be done sufficiently well in much plainer ways. The concept of “programming rule = society rule” worked better in theory than in practice.

Take a look at the board game Puerto Rico, and imagine how you’d code it in Inform.

This question has shallow and deep answers.

The shallow answer is, the rulebook mechanism is designed to let you manipulate a rulebook, run it, and then reset it. That’s a procedural rule. Allowing a permanent change (in addition to procedural rules) would be more complicated. Possible, certainly, but extra work.

The deeper answer is, this sucks because this mechanism is slow. (Making it more complicated would make it lower.) This is why procedural rules are now deprecated. So take a step back.

Any “now”-style change can be recast as a rule replacement with a condition. E.g.:

  When the foo scene begins:
    now the bar rule is not listed in the baz rulebook.
  The bar-prime rule is listed instead of the bar rule in the baz rulebook.
  This is the bar-prime rule:
    if the foo scene has not begun:
      follow the bar rule.

It’s more verbose, but it’s closer to the I7 principle of having conditional declarations where possible. (See also, “why can’t I write ‘now understand “x” as Y’?” “because a conditional understand is better.”)

It’s also much easier to compile into efficient code.

The drawback is that if you want your rule changes to achieve combinatorial explosion, you need to do much more work. (You have to write out all the combinations.) But as Ron said, rulebooks aren’t the only way to do that, and probably not the best way either.

As you’ve probably seen, I proposed a cleaner syntax for this on the uservoice forum. For this example, it would be:

  The bar rule doesn't apply when the foo scene has begun.

Still a conditional declaration, with the same meaning, but it avoids having to declare the extra rule.

The current rulebook implementation is undeniably powerful, but it’s also inefficient, painfully so in Javascript interpreters. Luckily most things that it was designed to do could be done without procedural rules, and should still work if rulebooks were compiled to single functions.

I think the best approach is to optimise for the majority use case and for performance. Luckily they both point in the same direction this time! If the rulebook implementation was rewritten and then genuine use cases were found that just couldn’t be solved with it, then we’d start to look at more options. It’s much better when complexity is only driven by actual needs.

Changing the implementation to compile rulebooks to single functions should also free up a little bit of memory, which would be a plus for the Z-Machine authors out there.

I understand that there are simpler ways to do this for specific cases (which perhaps my original example implied). But yeah, as Zarf points, out, this is most interesting when there are an exponentially large number of possible outcomes. I’m imagining a game where a player can arbitrarily change rules in many combinations, not just the particular ones I as an author thought of and wrote specific exceptions for.

(I suppose you could hack up some sort of code generation algorithm that would create a huge number of rules for you for all of the individual combinations. But this obviously would not scale very well.)

Also, I understand why procedural rules are slow: they have to be run every time anything happens anywhere to check if this is the sort of situation where this rule applies. But wouldn’t simply removing a rule from a rulebook with a “now” statement (assuming this worked) not cause this problem, since the change would be permanent, not constantly needing to be re-checked?

I mean, I imagine that the rules in a particular rulebook must be stored somewhere in memory (“Consult rule 1, then rule 2, then rule 3”) and that a procedural rule with an “ignore” phrase is overriding this in a temporary fashion (“Consult rule 1, then rule 3”). Why can’t this temporary override be permanent? Is it just something inherent to the z-machine’s memory structure?

Anyway, I’ve found an okay solution that uses a combination of tables, custom values, and objects to achieve a similar effect, but it feels very un-Inform 7.

Does this syntax work? It would get you partway to Zarf’s syntax:

To decide whether a rule applies: yes.

You’d still have to add “when the foo rule applies” to the condition of every rule. Kind of a drag, but maybe it would be usable.

If you’re manipulating a lot of different rules using an in-game mechanism, perhaps a property would be better:

A rule can be disabled.

Can rules have properties? If not, you’d have to create a variable or an object for every rule you want to change…

[code]A rule-switch is a kind of device. A rule-switch is usually switched on.

The foo-switch is a rule-switch.

This is the foo rule:
When the foo-switch is switched on…[/code]

Aaron, am I right in thinking that even in such an emergent system you would only be doing such things to a subset of the rules? Would it work to remove those rulebooks from the turn sequence rulebook tree (ie, that rulebook and any it calls) and then manually use follow the X rules depending on what the player has done?

The current rulebook implementation should be essential only if you want to randomly choose a rulebook and disable it, but that can’t possibly be a wise move.

It still implies that you have a rulebook engine which maintains a list in memory somewhere. And it has to be a dynamic list, which implies bounds-checking. It might look (in I6) something like:

for (ix=0 : ix < rbooklen : ix++) {
  rbook-->ix();
}

The goal, however, is to not have a rulebook engine for most cases. No array, no loop, no function calls. The compiler could generate a flat function with all the appropriate code pasted together. (You don’t need a separate function for each rule, unless that rule is explicitly invoked in some other way, and most aren’t.) This is a small improvement over the above – array offsets aren’t expensive – but it’s a small improvement many times over. And it opens up the possibility of the “everything is a rulebook, even individual library messages” model. That would be, IMHO, much more I7-y.

(Although I admit it won’t happen for a long time, maybe not in I7 at all.)

In the meantime, for your case, I think it’s reasonable to rig up a rulebook-like replacement structure out of other I7 constructs (tables or lists).

My syntax proposal is good for removing and adding rules. It doesn’t help with rearranging them, and I’m not sure if that’s needed for Aaron’s game plan.

But you’re right, you can rig up the same thing now by adding conditions to the rules when you define them. (Could be a “to decide” phrase, could be an activity, could be a lot of things.) The point of my proposal is to make it easy to do that to already existing rules, ie., those in the Standard Rules. Currently you can only do that with a wholesale replacement, which is tedious. If you’re writing the rules yourself, that problem doesn’t exist.

The logical extreme end of this is building a mini-compiler within your game that the player unwittingly programs.

Yes, but if said feature existed and worked that way, they would be the procedural rules. I don’t know why the PR were coded to work the way they were. When I wrote Repeat Through A Rulebook I was surprised to find that rulebooks were implemented as an array, not linked list, and procedural modifications were NOT done by modifying the array either. (Such as by temporarily swapping an element value with the Little-Used Do Nothing Rule, for ignore.) I see that array implementations take less memory than linked-lists, especially since there isn’t any linked list code in a Z-machine game at all (unless you specifically use { lists } in which case the corresponding .i6t file is brought in). OTOH, the check-the-stack-of-changes-upon-each-rule-invocation doesn’t seem any more elegant.

Ya know, I might be able to re-code procedural rules with the temporary-swap method. There’s an feature in I6 inclusions using {-allocate:foobar} that gives a bit of memory just large enough to point to a rule. So if I implement the Ignore phrase to swap to it, and Reinstate to swap back… and a “repeat through ignored rules” phrase to reinstate all ignored rules upon the beginning of a new turn… Hm.

It seems like this will be easy:

"test" by Ron Newcomb

Right Here is a room.

Include (- Global RuleIterator; Global RBinquestion; -) after "Definitions.i6t". 

To ignore (R - a rule) from (RB - rulebook): (-
	{-allocate-storage:IgnoredRules}
	RBinquestion = rulebooks_array-->{RB};
	for(RuleIterator = 0 : (RBinquestion)-->(RuleIterator) ~= NULL : RuleIterator++)
	{
		if ((RBinquestion)-->(RuleIterator) == {R}) {
			I7_ST_IgnoredRules-->({-advance-counter:IgnoredRules}) = {R};
			(RBinquestion)-->(RuleIterator) = LITTLE_USED_DO_NOTHING_R;
			break;
		}
	}
-).


To repeat through (RB - a rulebook): (-
	RBinquestion = rulebooks_array-->{RB};
	for(RuleIterator = 0 : (RBinquestion)-->(RuleIterator) ~= NULL : RuleIterator++)
		print (RulePrintingRule) (RBinquestion)-->(RuleIterator), "^";
-).

When play begins:
	say "The Carry out looking rulebook now contains:[line break]";
	repeat through the carry out looking rules;
	ignore the room description heading rule from the carry out looking rulebook;
	say "[line break]The Carry out looking rulebook now contains:[line break]";
	repeat through the carry out looking rules.

Hm, if I ignore more than one rule from a rulebook, I won’t know which blank spot is for which, so I won’t know in what order to reinstate…

Thoughts?

Submitted extension “Ignored Rules” to inform7.com. I only implement ignore & reinstate, as they’re the most useful. They work a little differently from the standard versions. One, ignore is permanent until reinstate – they don’t “reset” at the beginning of the next turn like the standard versions do. Two, we must give ignore & restate what rulebook is being affected. This was because, while possible to iterate through all 1,000 or so rules in Inform looking for one, the player would likely notice the slowness. So now the author must specify so the runtime only has a few places to look. Three, the phrases can be used anywhere, not just from the procedural rulebook, and take effect immediately.

Note: it’ll usually remain quiet if you, say, reinstate a rule already reinstated, or ignore a rule that doesn’t appear in the provided rulebook. It will toss a runtime error if you try to reinstate a rule into the wrong rulebook.

When the I7 dev team takes away the procedural rules, I don’t know if this extension will remain working or not. Specifically, if the dev team takes Danii’s suggestion to turn a run of rules into a single monolithic I6 function, the extension will break. However, if the dev team merely converts the FollowRulebook() functions to basically nothingness, it’ll still work. In the former case, if I7 creates some sort of property of rulebooks, not unlike the “default outcome” from 18.10, “Success and failure,” the author could specify select rulebooks to remain mutable at runtime.

Thanks, everyone, for the discussion (and to Ron for the awesome extension). This has led me through an interesting journey, Five Obstructions style, through a number of different techniques for producing similar effects. It’s interesting how many different ways there are to do something in I7…

A bactrian language, like I said. :slight_smile:

Why not dromedary?