Help with a 'to decide' phrase for UNDO

When I say ‘help with’, I actually mean, I want you to produce the code for me :slight_smile:

So what I’m after is a to decide phrase that will reveal whether an UNDO is possible.

i.e. Is there an UNDO state on the stack? Or is there not, because the player has done nothing yet, or they have already UNDOne back to the start or the limit of their UNDOs?

Thanks much.

-Wade

I’m not sure to get what you want, because I7 library already keep track of # of undos, and produces “You can’t “undo” what hasn’t been done!” if the UNDO stack is empty… so, I guess that you are after the UNDO handling routine in I7/10 library, I’m right or not ?

Best regards from a puzzled
Dott. Piergiorgio.

To clarify: I cannot program in I6. There is no specific I7 call I can make to find out if the UNDO stack is empty. The code that checks it is in I6 and it is connected to other things I don’t want. By the time it tells me the stack is empty and prints that message, it’s too late. Undo Output Control cannot intervene at the correct moment for my purposes either.

I guess I want just the check, which will be a tiny bit of I6 code, with an I7 wrapper on it so I can get the answer as a to decide phrase.

To decide whether the undo stack is empty:
	if blah (some i6 hook):
		decide yes;
	decide no;

(some i6 code...)

-Wade

The handling of undo saves (in terms of where the information is kept) is the responsibility of the interpreter. Assuming you are using Glulx, the game’s only interaction with that functionality is via @saveundo and @restoreundo (or equivalents), and indirectly via one of the gestalt codes.

So, it is possible to test whether the interpreter supports undo, and it is possible to test whether a @restoreundo was successful, but I don’t think there’s any way to directly test whether or not there is a “ready” save state. (EDIT: This is wrong as of mid-2022. See @hasundo, @discardundo opcodes in Glulx or the current Glulx specification for details.)

You can try to prepare this information indirectly, though, by tracking what happens when the relevant opcodes are executed. Here’s some 6M62 code to demonstrate:

Include (-

Global saveundo_ready = NULL;

-) after "Definitions.i6t".

Saveundo ready code is a number that varies. The saveundo ready code variable translates into I6 as "saveundo_ready".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Glulx.i6t: Undo
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ VM_Undo result_code;
	@restoreundo result_code;
	if (result_code == 1) saveundo_ready = false;
	return (~~result_code);
];

[ VM_Save_Undo result_code;
	@saveundo result_code;
	if (result_code == 0) saveundo_ready = true;
	if (result_code == -1) { GGRecoverObjects(); return 2; }
	return (~~result_code);
];

-) instead of "Undo" in "Glulx.i6t".

To decide whether saveundo state has been saved:
	if saveundo ready code is 1, decide yes;
	decide no.
	
When play begins:
	showme whether or not saveundo state has been saved.

Every turn:
	showme whether or not saveundo state has been saved.
2 Likes

Thanks. I’ll have to have a bit of a poke. I quickly tried dropping this in the I10 project, changing
-) instead of "Undo" in "Glulx.i6t"
to
-) replacing "Undo".
then got

In the sentence ‘if saveundo ready code is 1’ [](source:/Users/wadeclarke/Library/Inform/Extensions/Wade Clarke/Guide Mode.i7x#line431), I was expecting to read a condition, but instead found some text that I couldn’t understand - ‘saveundo ready code is 1’.

when compiling.

-Wade

There’s a new @hasundo opcode, which has decent support already, but you can’t assume all interpreters will have been updated yet.

2 Likes

Cool. I’ve found I was able to solve this problem with some of the jiggery-pokery I already have in place for my Guide Mode extension. I’ll just glance at it all with fresh eyes tommorrow then post it in the other topic.

-Wade

Sorry – I thought you were still on 6M62. (Also, it turns out I missed part of the code when pasting it above. Belatedly fixed.)

@Dannii, thanks for pointing out the new opcodes! I didn’t realize I had such an out-of-date spec document for Glulx.

There is an I6 global called IterationsOfTurnSequence which tracks the number of turns that have been commmited to the undo stack, so

To decide whether the undo stack is empty: (- (IterationsOfTurnSequence==0) -).

Inform 6 also tracks a global called undo_flag which can have the values 0, 1 or 2.

0 means that undo is impossible, either because the interpreter doesn’t provide it or because the story disallows it.

1 means that undo is impossible because the interpreter’s last attempt to save an undo state failed.

2 means that a saved undo state is theoretically available

Note that undo_flag is only set after the first attempt to save an undo state, so will always be 0 before and at the time of the first prompt. Consequently those of the following phrases that include undo_flag (all of them except the first) will only be useful after the first turn has taken place.

So:

To decide whether the story disallows undo: (- (((KIT_CONFIGURATION_BITMAP)&(PREVENT_UNDO_TCBIT))~=0) -). [This information is only available in Ver 10]
To decide whether undo is available: (- (undo_flag==2) -).
To decide whether the last save of undo state failed: (- (undo_flag==1) -).
To decide whether undo was blocked:  (- (undo_flag==0) -). [Either the story blocks it or the interpreter doesn't implement it]
To decide whether undo was impossible because the interpreter does not implement it: (- ((((KIT_CONFIGURATION_BITMAP)&(PREVENT_UNDO_TCBIT))==0) && (undo_flag==0)) -). [Again,  Version 10 only]

That in theory the undo stack isn’t empty and undo_flag==2 doesn’t guarantee that an undo will succeed, because if for example the interpreter allows only one ‘undo state’ to be stored, an attempted undo may still fail on the second consecutive ‘undo’ command with the message " ‘Undo’ capacity exhausted. Sorry!", but this is a situation that can only be tested for dynamically by attempting and failing an undo (in this case the routine attempting an undo, VM_Undo(), returns 0) because the interpreter doesn’t communicate to the story how many undo states it can save

2 Likes

Addendum- if compiling to Glulx, to make use of the opcode mentioned by @Dannii:

To decide whether a stored undo state is available: (- (HasUndo()==0) -).
Include (-
[HasUndo rv;
	@hasundo rv;
	return rv;
];
-).
1 Like

Addendum 2- again if compiling for Glulx, the following may be useful if it’s necessary to know before the first turn whether undo will be available from the interpreter assuming the story allows it:

To decide whether the interpreter provides undo: (- (ProvidesUndo()==1) -).
Include (-
[ProvidesUndo rv;
	@gestalt 3 0 rv;
	return rv;
];
-).

Probably better to check the gestalt first, so older terps don’t crash on an unknown opcode.

1 Like

That’s a different gestalt:

To decide whether the interpreter provides hasundo: (- (ProvidesHasUndo()==1) -).
Include (-
[ProvidesHasUndo rv;
	@gestalt 12 0 rv;
	return rv;
];
-).
1 Like

i suspect that the bubbling beaker #7 is soon to be assigned… :wink:

Best regards from Italy,
dott. Piergiorgio.

Right, that explains a lot!

For 13 years I’ve been battling with first turn nuisances. Depending on what you did before UNDOing and crashing back into the start, or just in the vicinity of the first turn, you’d see different messages, get different counts and have to account for multiple circumstances, just to make things like tutorials and early events work properly, and print non-stupid messages (because messages that read like they can’t even tell what whether the game’s started, or what happened during the first few turns of the game, read stupid.)

-Wade

I think you should be able to prevent this by resetting IterationsOfTurnSequence to zero after your introductory sequence- which should block any attempt to Undo further back than that point with a ‘You can’t “undo” what hasn’t been done!’ message.

1 Like

I have tried this and it does work. You just have to be careful to reset IterationsOfTurnSequence to zero after it is incremented in the turn sequence- otherwise at the end of the turn it will be incremented to 1, allowing undo to happen on the next turn, and a successful undo will restore the previous value of IterationsOfTurnSequence along with the rest of the game state, so the reset will have had absolutely no effect.

Actually, IterationsOfTurnSequence is iterated as the very last thing to be performed in the main loop, after the turn sequence rulebook has completed, so in practice the reset must be done at the start of the next trun sequence, e.g. before reading a command:

Undo-stack is a number that varies.
The Undo-stack variable translates into I6 as "IterationsOfTurnSequence".

Before reading a command when the current action was waiting: now Undo-stack is 0.

The above example code means that at the beginning of the next turn after a waiting action, IterationsOfTurnSequence is reset to 0 and undo is not possible on that turn, furthermore, although undo will be available on subsequent turns a sequence of undos will not rewind back beyond this point.

The previously-saved undo states do remain on the stack, so if IterationsOfTurnSequence were to be artificially increased again, those previous undo states would once more become available.

1 Like