How to be sure of whether an action has taken place, and thus time should tick?

Because in some scenarios the player will be under time pressure, I’m keen on reliably skipping the every turn rulebook when an action has failed so that time doesn’t tick.

When the action failed because the parser never got as far as successfully interpreting the player command as an action - no problem. When the action failed because one of my own rules - no problem, I have been consistent in manually setting a flag that causes the every turn rulebook to be skipped.

However, I have now realised that this approach isn’t enough because there are lots of post-parser ways the standard rules can cause actions to fail, but they don’t set any such flag.

However, I believe such actions result in rulebooks ending with failure. I’m starting to think that the only way I can do this properly is to check for whether an action has completed successfully, and only run the every turn rulebook if so.

Questions:

  1. Do you agree this is the right approach? It’s going to be hard for me to do this even if it is the right way, because I have never previously thought much about success and failure of actions and my current code is not at all consistent in making actions fail or succeed as appropriate because for my purposes so far it hardly seemed to matter.

  2. Am I right that that standard rules only fail when the attempted action just didn’t make sense so could not be physically attempted (e. g. an attempt to take something inside a locked closed box), and don’t fail for reasons where the failed attempt would actually take time?

  3. Are there likely to be difficulties, when, for example an action triggers other actions? Will I be able to be sure to monitor the outcome of the main one and not the others? For example, when the player command is inherently multiple actions (e.g. “take all”), will it be possible to monitor whether at least one succeeded and all failed?

As you can see, I’m just not that clued up about success and failure and would like advice before I start on a system that might end up not being the best way or not even working.

I think perhaps not. I will tell you my approach to the same issue you describe (wanting to stop every turn rules firing in particular circumstances), which comes at it from the other side.

So first, remember that Inform’s approach is that out of world actions never cause every turn rules to run, nor do parser errors and disambiguation etc., but everything else does. It doesn’t matter what the action was, or whether it succeeded or failed to the player’s eyes, (you tried to take something - did you get it or not?.. different to Inform’s internal success/failure concept) the every turn rules will run afterward, and thus Take Time.

From Eric Eve’s Variable Time Control extension, you can add in the concept of No Time, short for Take No Time; the opposite of taking time. His rules track invented game time in minutes and seconds, but I don’t even use those features. I just add one adjustment that makes it so that if my code encounters the NO TIME signal during a turn, the every turn rules won’t run for that turn. That requires only this extra bit of code:

This is the NEW every turn stage rule:
	unless time-reset is true:
		follow the every turn rules;

The NEW every turn stage rule is listed instead of the every turn stage rule in the turn sequence rulebook.

Having installed his extension and the above code, you can manually flag outcomes/actions that you want to take No Time. As an example, I might want EXAMINE to not take time (i.e. not cause the every turn rules to fire), so the player can review objects/people/their inventory during a time-critical scene.

So to get that effect, I can just throw in a line like this:

Carry out examining:
	take no time;

Now the every turn rules won’t run after any examine.

The extension also provides a way to poke the no-time flag into any piece of text. So any time you create a printed response for the player, you can chuck in a no-time, and no matter where/how this is coded during the turn, that will stop the every turn rules for that go. Cumulative No Times encountered during one turn don’t add up, either - they will just stop the every turn rules once - so there’s no danger of having to count them.

Here’s an example. Say the player’s in a fight and will die in one blow, and there’s a weapon on the wall they can grab. You don’t want every GET in the game to take NO TIME, just this one that will allow them to arm themselves before the every turn rule has the enemy attack and kill them. So, in the text for when they grab the weapon, you poke in a NO TIME phrase:

Report taking the glaive for the first time:
	say "[take no time]You seize the Glaive from the wall. The Beast looks perturbed as he realises you have acquired the ability to kick posteriors.";

If time is important across the game, I usually go through the library messages and throw the [take no time] into responses wherever it’s needed. That’s easier to me than adding Carry Out rules to every action. And it basically means I’ve created a system. Every regular action will or won’t take time. After that, any new text or actions I add to the game, I can throw in a take no time where it’s needed.

I use this so much I usually abbreviate it to nt.

To say nt: take no time;

Then I can put a bracketed [nt] in response texts instead of the whole [take no time]

PS Make sure you DON’T throw a take no time into an out of world action or its response, or the player will actually get an extra free turn!

-Wade

2 Likes

Thanks Wade.

Your suggested way makes a lot of sense and it’s pretty much the exact same thing that I’m already doing (“I have been consistent in manually setting a flag that causes the every turn rulebook to be skipped.”) The key thing I wrote that perhaps wasn’t clear enough was “there are lots of post-parser ways the standard rules can cause actions to fail, but they don’t set any such flag.”

Take for example, me. “Take me” results in the standard rules producing something like (from memory) “You are always self-possessed.” These kinds of responses are my problem. I don’t think that should take any time, because it’s an explanation that the action doesn’t make sense so the player turn didn’t represent a time-taking effect. From a player perspective it shouldn’t matter whether the parser or later standard rules determine an action attempt to be nonsensical, for determining whether it took time, but with the approach you and I are currently using, it does matter. Nonsense detected by the parser takes no time; nonsense detected later does.

As an aside, I’m interested to see you and a well-known library using say phrases like “[take no time]” for side-effects (variable setting) rather than just to say something. I stopped doing that after I had an annoying bug that seemed to go away when I stopped doing it. Because it seemed like variables that got set in say phrases were sometimes getting set unexpectedly, I ended up with a theory that sometimes some text constants get internally said without printing, which doesn’t matter if all they do is print, but does of course matter if they have side-effects. Perhaps I was just wrong about what was causing my bug.

Cheers,

Ben

Sorry, I don’t follow this example. If you scan the library messages, when you see the ‘You are always self-possessed’ message, you, the author, act on it. You never want that to take time. You can drop an nt token into it and then it can’t. This is why I really like attaching the token to texts, and why my starting place for applying this system is scanning the library responses. That’s pretty much it – there is no time-taking output text hidden elsewhere in Inform. Read every library message, decide whether time should be taken or not if that message appeared (ignoring parser error messages. They are easy to avoid.). I don’t recall ever having to labor over this decision in any particular case. Nonsense detected later still takes no time. Have I missed something? This is the reason I wrote at length, as it seemed you were making some distinction that didn’t need to be made.

It’s a feature I like. But I expect I know what happened to you, as it’s happened to me and others, and caused the same confusion.

Inform’s evaluating a say phrase – within a text field – in advance of actually printing the contents of the field causes the code to be run twice (once in evaluation, once in the printing). Matt Weiner explained this to me. Basically if you avoid doing things that will muck up if the code part of them is run twice (e.g. incrementing a counter from withing a say phrase to determine what to print within that say phrase is not safe, and the most common error case, but setting a variable to one other value IS okay, or just reading a flag and printing text in response without changing that flag is also okay)… it is okay to use say phrases that run code even from within room or people description text fields.

Here is a demo showing three cases. (just go east twice, but it only makes sense in looking at the code, too)

Summary
Demo 1 is a room. "(This room shows that incrementing a counter by running code from a say phrase embedded in printed text is not safe.)".

Jenny is a woman in Demo 1.



Initial appearance of Jenny is "[jenny desc]".

flag of Jenny is initially 0.

To say jenny desc:
	increment flag of Jenny;
	if flag of Jenny is 1:
		say "Jenny says, 'This is the first time you've seen me.'";
	otherwise if flag of Jenny is 2:
		say "Jenny says, 'You've seen me before!'";

Demo 2 is east of Demo 1. "(Code for this room does what was attempted in the first room, but this time safely, using code embedded in a Rule for writing a paragraph about...)". 

Tarzan is a man in Demo 2.

flag of Tarzan is initially 0.

Rule for writing a paragraph about Tarzan:
	increment flag of Tarzan;
	if flag of Tarzan is 1:
		say "Tarzan says, 'This is the first time you've seen me.'";
	otherwise if flag of Tarzan is 2:
		say "Tarzan says, 'You've seen me before!'";

Demo 3 is east of Demo 2. "(Reacting to the value of a counter to determine what to print in a say phrase IS safe, so long as we don't change the counter's value using code run from within the same printed text)."

Bobo is a man in Demo 3.

Initial appearance of Bobo is "[bobo desc]".

flag of Bobo is initially 3.

To say bobo desc:
	say "Bobo says, 'My flag is currently set to [flag of Bobo].";

Test me with "e/e".

Room 1 is the classic error case. We write code hoping to make Jenny say the right thing as a flag increments each turn, but she ends up jumping immediately to her second quote (You’ve already seen me!)

Room 2 shows the same scenario done safely with a ‘rule for writing a paragraph about’ instead.

Room 3 shows it’s safe to use a say phrase (with code) embedded in a description field to produce varying text so long as we don’t go, for instance, incrementing a counter at the same time.

Throwing [nt] say tokens into texts is totally safe. There’s no incrementing or if-thenning - the token just sets a flag to a value.

-Wade

2 Likes

This is true. Text tokens can be expanded for reasons other than printing, typically to compare the text they appear in to some other text- and if text expansion runs code, this can lead to unexpected consequences because the code is run more frequently than intended, or in situations the author did not expect.

Fortunately, Inform provides an undocumented means to neatly side-step this problem:

if expanding text for comparison purposes....

tests whether the text is being expanded to print or for other purposes and if this condition is checked at the start of code invoked by a text expansion, this allows for skipping the rest of the code unless the text is actually being printed.

This allows you to, for example, increment a counter only if the the text is actually being printed.

3 Likes

Huh! I’ve been here a decade and still not clocked that phrase before, though a search shows me this business has come up repeatedly. Anyway, thanks again!

-Wade

1 Like

Yeah. It’s not exactly a bug, but the fact that it’s not mentioned as a caveat in the WI documentation on text substitutions is a glaring oversight.

The technical advice here is all really helpful (at least, I learned a bunch!) but in case it’s of use I wanted to offer some thoughts on the design side of things. Briefly, I think going through and labelling every single action or error as taking time or not seems like a lot of work that might actually make the game feel less consistent and reasonable to the player than if you just stuck to some simpler cases.

In the traditional implementation (parser errors and out of world actions don’t take time, everything else does), the player very quickly understands what causes the timer to click forward and what doesn’t – so it’s easy for them to figure out that they have X number of turns to solve whatever puzzle is in front of them. And if you’ve done a good job of signposting that this is a dangerous situation, probably they’ve got a save, or can abuse UNDO, to get through it even if they fail the first time.

In the alternative world where you’ve deemed that a bunch of unsuccessful actions don’t take time, this can be harder to parse, though, since the line between a successful and unsuccessful action isn’t always obvious and your judgment calls might not be clear to the player. TAKE ME is a clear example, sure, but what about OPEN SANDWICH (the player has an idea involving using the cheese from the sandwich, but you haven’t implemented it as a container), PUSH CRATE (player’s looking for a secret door behind it, but it’s implemented as scenery), GO UP (there’s no implemented exit up, but the player typed that to try to climb over an obstacle), etc.? And some of these actions are things that should take time in the world (trying to manhandle the crate, e.g.) even if they’re not being resolved successfully – like, the player had an idea, they tried something, and it didn’t work. And from the player’s perspective, they’re going to be trying a bunch of stuff, and sometimes a timer will be firing and sometimes it won’t, meaning it might take a while to realize what the rules are and how much time they have.

This can also mess with your pacing, which I’m assuming you want to be tight in a high-stress, timed sequence. In my IFComp game last year, I implemented a big puzzle where the player needs to do a bunch of things under time constraints, with a steadily-escalating sequence of events signaling increasing urgency and slightly changing the state of the world. In the comp release, I took a similar (though much simpler) approach to what you’ve outlined, by exempting a number of actions from advancing time, including examining.

I only found out this was a bad idea after I real folks’ transcripts. The trouble was, when players were let loose on this puzzle, almost without exception they spent a bunch of turns at the beginning Xing everything in sight, which took a long, long time, without any of the escalating events firing and making them feel like they had all the time in the world. Then when they started doing things, suddenly all the events started happening really close together, meaning the planned steady escalation of tension never happened. I actually wound up going back to the traditional, zero-work approach in a post-Comp release, because this one common-sense convenience for the player wound up really messing up the pacing.

Anyway, this is a lot of words that basically boil down to considering the design implications here before embarking on the significant task of adjusting every single possible action in the game. Maybe run a version of the puzzle by a beta tester or two and see what they think, because I think the real-world experience of this kind of stuff can play out differently from how you expect!

Hope this is useful and good luck with the game!

2 Likes

There is an important ‘gotcha’ subtlety to this, which occurs when your code both changes the game state (e.g. increments a counter) AND prints something varying according to that change.

This is because Inform needs to be working with an accurate and updated version of what is about to be printed when it expands your text for comparison- otherwise it will be comparing against something that is not what is about to be printed, or against an out-of-date version (i.e. what was printed last time)

For this reason, simply putting a condition to skip ALL the code until something is actually being printed may not work as expected. In the example above, this may lead to problems:

To say jenny desc:
	if not expanding text for comparison purposes:
		increment flag of Jenny; 
		if flag of Jenny is 1:
			say "Jenny says, 'This is the first time you've seen me.'";
		otherwise if flag of Jenny is 2:
			say "Jenny says, 'You've seen me before!'";

because now nothing at all is ‘said’ if text is being expanded for comparison purposes- meaning the token is expanded to “”- and Inform may act on this with unexpected responses.

Even this better-looking effort causes difficulty, because the flag is not incremented until something is actually about to be printed, so comparisons run ahead of that are working either with “” (when flag of jenny is 0) or with what was last printed rather than what is about to be printed:

To say jenny desc:
	if not expanding text for comparison purposes:
		increment flag of Jenny; 
	if flag of Jenny is 1:
		say "Jenny says, 'This is the first time you've seen me.'";
	otherwise if flag of Jenny is 2:
		say "Jenny says, 'You've seen me before!'";

expands to “” for comparison until ‘This is the first time you’ve seen me.’ is printed, then ‘This is the first time you’ve seen me.’ until ‘You’ve seen me before!’ is printed.

One solution is to increment the flag (or otherwise update the game state) immediately AFTER actually printing something, so any subsequent expansions for comparison are working with updated text, which finally gets printed before the flag is incremented again:

To say jenny desc:
	if flag of Jenny is 0:
		say "Jenny says, 'This is the first time you've seen me.'";
	otherwise if flag of Jenny is 1:
		say "Jenny says, 'You've seen me before!'";
	if not expanding text for comparison purposes:
		increment flag of Jenny; 

In even more complicated situations, where it’s absolutely necessary to run the ‘change the game state’ code immediately BEFORE saying the text of a text expansion rather than after saying the last one (because, for example, the code depends on the current location) you may need to maintain a global flag holding the context of the last expansion, so that the game state is changed when expanding for comparison purposes, but ONLY when the last expansion was actually printed (or on the first ever expansion):

last_jenny_desc_expanded_for_comparison_flag is initially false.
To say jenny desc:
	if last_jenny_desc_expanded_for_comparison_flag is false:
		increment flag of Jenny; [only increment the flag on the first occasion text is ever expanded, or on the first occasion following an actual printing]
	if expanding text for comparison purposes:
		now last_jenny_desc_expanded_for_comparison_flag is true;
	else:
		now last_jenny_desc_expanded_for_comparison_flag is false; [record the context of this text expansion, for checking next time]
	if flag of Jenny is 1:
		say "Jenny says, 'This is the first time you've seen me.'";
	otherwise if flag of Jenny is 2:
		say "Jenny says, 'You've seen me before!'";

EDIT: This last method is the one used by Inform’s built-in sequential text-modifying expansions such as [one of]...[or]...[or]...[stopping], in order to make sure none of the alternate texts get skipped over for printing due to the text being ‘expanded for comparison purposes’.

2 Likes

Thanks so much for so much good advice, everyone!

I think we are talking past each other a little, not because we don’t understand each other, but because although I understand exactly what you mean, whereas you consider this method acceptable, I would rather avoid doing that. It would involve going though the whole standard library and dropping such tokens in all over the place, but I generally like to mess with the standard library as little as possible, and I think this might be harder to do than one might envisage because a fair few of the standard responses are procedurally generated piecewise by code I don’t follow. But I agree, it could in principle be done this way, and I’m not confident I’m right to be wary of this approach.

And @DeusIrae , thanks also for the discussion of your own experience, which is from exactly the sort of situation I’m trying to create, and so very relevant to me, and indicates I need to give more thought to the actual game design (and perhaps avoid getting bogged down in implementation details for something I anyway shouldn’t be trying to implement). There are various reasons I still think this could work for me - for example my game is pure audio, and I’ve been considering using audio cues to indicate whether time has passed on each turn or not; I also think it means my game might (if I’m lucky) get a different audience to the typical IF consumer, who aren’t so examine-happy.

@drpeterbatesuk and @severedhand - I appreciate very much that detailed breakdown of the text-expansion side-effects situation. I’m pleased that I wasn’t just imagining things when this was happening to me before. Especially given Peter’s most recent post, which I haven’t yet fully followed, I think I might continue to avoid this practice, although I agree with Wade that it’s an elegant trick if you can trust yourself to use it right!

3 Likes

Fair enough! The good thing about making games is that in the end, if it works, it works. The player won’t know how you made something happen, so we can all use our own preferred methods. My ‘going through the standard responses’ approach is convenient for me because I’m usually changing a bunch of them to fit a character, anyway. Also, it’s probably because my first Inform game (Six) was heavily timing dependant, with NPCs wandering around and running away from each other. So I really did have to flag all actions as taking time or not to support the simulation.

Re: the ‘if expanding text for comparison purposes’, as well as drpeter having shown the caveats even to that approach, I’ve now been comfortably doing things the avoiding-that-situation-in-the-first-place way for about a decade, so I’m likely to continue.

-Wade