'subroutines' and every turn

I’m making good progress in my learning journey here, but there are still some fundamental aspects clouding and slowing my thinking. Please set me straight if the following assertions fail: The concept of a subroutine seems to be indirectly handled by I7 via rules invoked by actions (not by states). Changes of state only do so when they’re caused by the actions of some actor, and any reaction will spring from the source of the change and not, say, a change in a variable. If somewhere in the code a value changes, one cannot have that, by itself trigger something else directly.

For example, if the player does something that causes an NPC to become hostile, programming a reaction is straightforward; however, let’s say the player does something that changes some state (the ‘hostile’ flag) on various types of NPC’s (let’s say dozens of them, but not all) spread throughout the map, who will start to converge on the player. My instinct is to try to create a subroutine to call the NPC to arms, but that’s not the way I7 works. I believe I should use the van helsing pattern documented elsewhere, but what is a good way to trigger (iterate) all of the various NPCs that may now be hostile? I’m hoping I won’t need to exhaustively enumerate each one directly in a rule firing every turn that looks at them all. I got it to work with repeat with hostiles running through persons: but I was not able to hit upon the right incantation for a subset with something like A guard is a kind of person. A Nasty guard is in the Belfry. Repeat with hostiles running through guards: This results in no matches. I had expected that ‘guard’ would function as a subset of ‘person’. I suppose I could set some attribute on the specific persons to work around this, but then what would be the point of having a kind of guard when I don’t seem to be able to use it?

Secondary question: When an every turn rule is certain to no longer be required for a period (e.g. the converging hostiles event is finished), is there a way to turn it off temporarily until needed again? E.g., When guards are again hostile, exhibit some hostility. If this is never expected to be a system performance issue, then it probably doesn’t matter.

It should. Can you post a snippet of code that doesn’t work?

Something like

Every turn when guards are hostile:
    do something or other;

where guards are hostile should be replaced by whatever condition you like.

3 Likes

A nasty guard is in the Belfry. just declares a thing called “nasty guard”. To instantiate the guard kind, you’d write: The nasty guard is a guard in the Belfry.

1 Like

There are also “Phrases” (chapter 11 and 22) for encapsulating some bits of code the way a subroutine does in other languages:

To spring the trap:
    say "'Sproing!' go the hinges and, with a flash of silver, the enormous blades whisk together!";
    end the story.

[and then use it with, e.g.]
Instead of entering the cage:
    spring the trap.
5 Likes

For temporary conditions and happenings, you could use the “Scene” mechanism (essentially a global variable); see 10.1. Introduction to scenes.

You can use that in conjunction with phrases to articulate complex conditions.

Here’s a small example:

The Belfry is a room.

A guard is a kind of person.

A guard can be hostile or peaceful.

The nasty guard is a guard in the Belfry.
The nice guard is a guard in the Belfry.
The tall guard is a guard in the Belfry.

To decide whether the player aroused suspicion:
	if the number of hostile guards is at least 2, decide yes;
	decide no.

Instead of jumping:
	now a random peaceful guard is hostile.
	
Hostility is a scene.
Hostility begins when the player aroused suspicion.

When Hostility begins:
	say "You sense that the guards have become suspicious due to your shenanigans."
3 Likes

There are several subroutine-ish things in I7…

Function-like things (i.e., taking parameters and returning values):

  • Definitions take 1 parameter (some specified kind of value) and return a truth state (i.e., true or false)†
  • To decide what/which <kind of value> phrases take 0 or more parameters and return 1 value (but the <kind of value> can’t be a truth state; To decide if phrases create conditions, which isn’t the same thing as returning a truth state)
  • Rulebooks take 0 or 1 parameters and return nothing or 1 parameter
  • texts with adaptive text are really functions that return a text on demand when you output them or ask for their substituted form

It’s a pain but if you really want multiple-parameter output you can make the one parameter an object with multiple properties, or a list of objects (or other kinds of values)… or, well, get used to an over-reliance on global variables.

Subroutine-like things (can’t return values)

  • To phrases/To say phrases take 0 or more parameters and execute some amount of code. Each can do everything the other can with one important distinction: you can use say phrases for adaptive text within texts by invoking them within brackets. (With a related second important low-level distinction: this provides the easy way to get text that’s output by print statements in Inform 6-level routines into Inform 7 text variables.)
  • Rules don’t take parameters on a per rule basis, but if they’re part of a rulebook that takes a parameter, they can get access to that parameter. A rule can be invoked discretely with follow the this-specific-rule-name rule;. A rule within a rulebook that produces a value may produce a value on the rulebook’s behalf, but even if the rulebook produces a value, any given rule doesn’t have to, and it’s common that many of them won’t.
  • Rulebooks can be viewed as one big subroutine. The rule preambles are essentially a flow control mechanism, determining which bits get run.
  • Activities are basically 3 rulebooks, and can be viewed as one even bigger subroutine. (They take 0 or 1 parameters but cannot return a value.)

The way you make an every turn rule get ignored when it’s not relevant is by doing nothing and not worrying about it. Say you have something that’s triggered only when the moon is blue and you’ve implemented it with every turn when the moon is blue:. But you know there’s just one period in the game when the moon will be blue, and once the moon’s no longer blue, there’s no need to check that anymore, so you don’t want that check wastefully consuming cycles for the whole game.

…except every scheme you come up with to check whether you need to make that check will still involve a check every turn.

You can make Blue Moon a non-recurring scene (that’s the default for scenes) as an organizational device, but the most straightforward way to implement related rules (other than the when scene begins and when scene ends rules) is still every turn when blue moon is happening.

So don’t write every turn rules of the form every turn when x is not the optimal solution to the travelling salesperson problem... and otherwise relax and don’t worry about it. (This isn’t to say that you can’t get into speed problems with large games, but you’d have to cross that bridge when you came to it by figuring out what the bottleneck for that situation was.)

† edited to clarify: well, sort of… Definitions are written to answer yes or no for some given test, but it’s not the case that they return a truth state per se. They create adjectives which can be used in multiple places in Inform 7, including as the basis for conditions.

7 Likes

Or, if you have multiple input/return values of different kinds (and so can’t pass a list as a single parameter/return value) and baulk at the idea of creating and passing back and forth one or more objects with multiple specific properties to carry those values, or of using multiple global variables, you could instead write your multiple input parameters/return values into a specific table defined for the purpose, and/or even under some citcumstances pass the table name as parameter or return value.

EDIT: to some extent, a table of values used in this way is rather like a battery of global variables in another guise, but since global variables are nowadays A BAD THING, this approach may feel more comfortable. And it does alleviate the gnawing concern associated with batteries of globals of creating possible namespace clashes unless their names are carefully chosen.

1 Like

It’s exactly the same thing, and IF game code doesn’t have the same issue with global variables.

Well, that’s a reasonable concern.

Yeah. I was being slightly tongue-in-cheek :wink:

Folks, all of this correspondence has proved useful to me and is adeptly demystifying swathes of I7 for me. I thank all of you for the excellent support and your non-toxic good natures.

I just realized that after successively marking nearly all of your replies as ‘solutions’, that only the most recently one so marked retains that label. A pity, as they all solved some important aspect of my multipart query.

2 Likes