Can you still 'stop game abruptly' in 6L38?

I’m getting ‘You wrote ‘stop game abruptly’ : but this is a phrase which I don’t recognise…’ etc

I searched the 6L38 change log but don’t see anything about this being cut, and there’s a still an example in the instructions that uses it.

-Wade

To stop the/-- game abruptly: (- quit; -)

I don’t know if it was removed from the standard rules… my feeling is it’s always been defined in extensions instead.

Yes, it’s defined in Basic Screen Effects by Emily Short (note that the example, “The Battle of Ridgefield,” includes that extension).

I suppose the new style would be “end the story abruptly,” but that seems exactly as worth bothering with as the maintainer is willing to do so unprompted.

(Though this makes me think of something–can an extension author deprecate a phrase?)

Yes any phrase can be deprecated.

Ah, right. It’s because I’m not using Basic Screen Effects, which would be a first ever. I’m using Zarf’s experimental Glulx Unified Input extension, which blows everything up.

I got around the absence of ‘stop game abruptly’ with ‘follow the immediately quit’ rule.

On a related note, do you know if there’s any difference between following the ‘immediately undo’ rule in your source, and just typing UNDO at the prompt? Because I’ve actually got no prompt… I’m forcing the former when the player hits ‘U’, and it’s not working. It say ‘previous move undone’ but the clock doesn’t roll back and it doesn’t undo.

The trouble is everything in this program is so irregular it could very well be something else, but I thought I’d check something obvious in vain hope that it’s not one of the weird things.

-Wade

How do you deprecate a phrase when writing an extension?

(Sorry to derail, wade–I don’t know the answer to your question.)

I already derailed my own thread :wink:

Edit: OK - I added this mechanism to Zarf’s demo game and it froze UNDO there too. So I’ll need to ask him.

-Wade

Though, now that I think of it, does your game even have turns? Do the “every turn” and “advance time” rules wind up running? Because given the description you gave of it before, it kind of sounded like you were skipping the turn mechanism entirely, which sounded like it would mess up the functionality of “undo” no matter how you invoke it.

Like this.

To say the/-- banner text (deprecated): (- Banner(); -).

Hope this helps.

Thanks for thinking of it, but actually what you’ve pointed out turned out to be one of the main reasons I’m trying Zarf’s extension.

You’re right that in my super hacky version, there was no concept of turn advancement.

With Glulx Unified Input, you can collect keypresses entirely in place of the parser, and they’ll convert to actions which cause turns to pass, can move you from room to room, etc… anything the normal game can do.

Now I’ve still hosed out the world model, but I’ve tried to keep the turn mechanism because that way I get ‘free’ undo management.

In Zarf’s Maze of Keys 2 demo game from this extension, there’s one room which uses the parser, but in the maze you use single keypresses to move you around.

What I just discovered 5 minutes ago is that typing UNDO in the parser-only room does indeed UNDO, but if you add a way to ‘immediately follow the undo rule’ down in the maze section, the turn just stays where it is. So, there’s something about the handling of the events in the keypress only section that’s interfering with UNDO. Turns do advance if you move around in the maze, but you can’t undo in there. So that’s the case with my project, too.

EDIT: Zarf, if you’re looking, I just swapped the ‘south’ command out of Maze of Keys 2 to install undo there. In other words, going south is meant to undo. But this mechanism doesn’t work. I just changed/added these bits:

[code] if C is special keycode down or C is Unicode Latin small letter s or C is Unicode Latin capital letter S:
say “(You try going south…)”;
handle the current input event as the action of wanting to undo;
rule succeeds;

wanting to undo is an action applying to nothing.

carry out wanting to undo:
follow the immediately undo rule.[/code]
-Wade

Thanks climbingstars!

I forget exactly where the parser or standard rules makes an undo save, but you can easily add them yourself. Check my simple unit tests extension for the I6 code.

Ah, thanks. I’ll have a look at that, too.

-Wade

The standard parser only does an undo-save in the Keyboard() routine. That’s after reading a line of input but not after reading a keystroke.

I’m not sure the immediately undo rule is supportable the way it is. Undo is tightly bound to the input cycle and I think it doesn’t make sense to call it during the body of the turn. No, I’m not sure why that ever worked.

The fix for UGI would be to check for an undoing action at the end of ParserInput(), at the point where it currently checks for UNDO1__WD. Then you could write a handling input rule and say “handle the current input event as the action of undoing”.

(Of course there is no undoing action in the library, so I’d have to stub one in…)

Sorry. I know this is messy.

There’s the additional mess that not all inputs are turns. In Maze of Keys, each keystroke is a turn; but when you hit a key to continue that’s not a turn. Scrolling around in a menu really doesn’t take turns. You don’t want to save undo points for those inputs.

Thanks for having a look, Zarf. Yeah, I’m only after having a turn pass when you press a key in the handling input loop and it executes an action, ala maze of keys. I’ve already got other kinds of keypresses going which don’t take turns. (UNDO is actually the main reason I started building this CYOA thing with GUI. Without UNDO, I could just go back to my ‘superhack’ :slight_smile:

That said, this new thing is still hacky, it just has the courtesy to execute actions and to have turns, which the superhack doesn’t. There’s only 1 room in this and that’s just there for it to compile. There are no room descriptions. Each choice node is a rule, and to reprint the current node you can just execute a ‘look’.

-Wade

It works because a successful undo acts as an imaginary go to and the code jumps to the VM_Save_Undo function, which is where the last save took place.

This is because you’re activating undo after the game state has been saved rather than before, so the undo isn’t actually going back a turn. You’ll need to undo before the game state has been saved in order for it to be effective. Try this.

[spoiler][code]“Maze of Keys 2”

Use scoring.

Include Unified Glulx Input by Andrew Plotkin.
Include Unicode Character Names by Graham Nelson.

Include (-

! ParserInput: block and await acceptable input. Returns an event in a_event; tokenized line data will be in a_buffer and a_table.
! This is a wrapper around AwaitInput which adds “OOPS” and “UNDO” support – features appropriate for the main parser input loop. It also permits the game to customize what kinds of input are accepted for that loop.
! This is called from Parser Letter A (primary command input) and NounDomain (disambig inputs).
! (Context-specific questions, such as YesOrNo and the end-game question, do not use this wrapper. They call AwaitInput directly.)
! In this function, unlike in AwaitInput, a_buffer and a_table are both mandatory. They may be either buffer/table (primary context) or buffer2/table2 (disambiguation context).

[ ParserInput incontext a_event a_buffer a_table evtyp nw i w w2 x1 x2;
! Repeat loop until an acceptable input arrives.
while (true) {
! Save the start of the buffer, in case “oops” needs to restore it
Memcpy(oops_workspace, a_buffer, 64);

	! Set up the input requests. (Normally just line input, but the game can customize this.)
	FollowRulebook((+ setting up input rules +), incontext, true);
	
	! The input deed itself.
	AwaitInput(incontext, a_event, a_buffer, a_table);

	! We have an input event now, but it could be any type. If it's line input, it's been tokenized.
	
	evtyp = a_event-->0;
	nw = 0;
	
	if (evtyp == evtype_LineInput) {
		! Set nw to the number of words
		nw = a_table-->0;
	}
	
	#ifndef PASS_BLANK_INPUT_LINES;
	! If the line was blank, get a fresh line.
	if (evtyp == evtype_LineInput && nw == 0) {
		! The old Keyboard routine cleared players_command here (to 100). I'm not sure why. If we're on buffer2/table2, the players_command snippet doesn't apply at all.
		EmptyInputParserError();
		continue;
	}
	#endif; ! PASS_BLANK_INPUT_LINES;
	
	! If this is line input, fetch the opening word.
	w = 0;
	if (evtyp == evtype_LineInput && nw > 0) {
		w = a_table-->1;
	}
	
	! Oops handling
	
	if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) {
		if (oops_from == 0) { PARSER_COMMAND_INTERNAL_RM('A'); new_line; continue; }
		if (nw == 1) { PARSER_COMMAND_INTERNAL_RM('B'); new_line; continue; }
		if (nw > 2) { PARSER_COMMAND_INTERNAL_RM('C'); new_line; continue; }
	
		! So now we know: there was a previous mistake, and the player has
		! attempted to correct a single word of it.
	
		for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
		x1 = a_table-->6; ! Start of word following "oops"
		x2 = a_table-->5; ! Length of word following "oops"
	
		! Repair the buffer to the text that was in it before the "oops"
		! was typed:
		Memcpy(a_buffer, oops_workspace, 64);
		VM_Tokenise(a_buffer,a_table);
	
		! Work out the position in the buffer of the word to be corrected:
		w = a_table-->(3*oops_from);      ! Start of word to go
		w2 = a_table-->(3*oops_from - 1); ! Length of word to go
	
		! Write spaces over the word to be corrected:
		for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';
	
		if (w2 < x2) {
			! If the replacement is longer than the original, move up...
			for ( i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i-- )
				a_buffer->i = a_buffer->(i-x2+w2);
	
			! ...increasing buffer size accordingly.
			a_buffer-->0 = (a_buffer-->0) + (x2-w2);
		}
	
		! Write the correction in:
		for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);
	
		VM_Tokenise(a_buffer, a_table);
		nw = a_table-->0;
	
		return;
	}

	! Undo handling
	! ### only if the player *could* have entered an UNDO command!
	if (evtyp ~= evtype_CharInput) {
		if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
			Perform_Undo();
			continue;
		}
		i = VM_Save_Undo();
		#ifdef PREVENT_UNDO; undo_flag = 0; #endif;
		#ifndef PREVENT_UNDO; undo_flag = 2; #endif;
		if (i == -1) undo_flag = 0;
		if (i == 0) undo_flag = 1;
		if (i == 2) {
			VM_RestoreWindowColours();
			VM_Style(SUBHEADER_VMSTY);
			SL_Location(); print "^";
			! print (name) location, "^";
			VM_Style(NORMAL_VMSTY);
			IMMEDIATELY_UNDO_RM('E'); new_line;
			continue;
		}
	}
	! Neither OOPS nor UNDO; we're done.
	return;
}

];

-) instead of “Reading the Command” in “Parser.i6t”.

The Kitchen is a room. “You are in a kitchen. An open trap door beckons you downward.”

Aboveground is a region. The Kitchen is in Aboveground.

Maze10 is a room. “You are in a maze of twisty passages, basically all alike.”
Maze20 is a room. “You are in a maze of twisty passages, all pretty much alike.”
Maze01 is a room. “You are in a maze of twisty passages, all basically alike.”
Maze11 is a room. “You are in a maze of twisty passages, all kind of alike.”
Maze21 is a room. “You are in a maze of twisty passages, more or less all alike.”
Maze02 is a room. “You are in a maze of twisty passages, pretty much all alike.”
Maze12 is a room. “You are in a maze of twisty passages, all alike.”
Maze22 is a room. “You are in a maze of twisty passages, all more or less alike.”
Maze32 is a room. “You are in a maze of twisty passages, all sort of alike.”
Maze03 is a room. “You are in a maze of twisty passages, kind of all alike.”
Maze13 is a room. “You are in a maze of twisty passages, all quite alike.”
Maze23 is a room. “You are in a maze of twisty passages, quite all alike.”
Maze33 is a room. “You are in a maze of twisty passages, sort of all alike.”

Maze12 is below the Kitchen.
Maze10 is west of Maze20.
Maze10 is north of Maze11. Maze20 is north of Maze21.
Maze01 is west of Maze11. Maze11 is west of Maze21.
Maze01 is north of Maze02. Maze11 is north of Maze12.
Maze02 is west of Maze12. Maze12 is west of Maze22. Maze22 is west of Maze32.
Maze12 is north of Maze13. Maze22 is north of Maze23. Maze32 is north of Maze33.
Maze03 is west of Maze13. Maze23 is west of Maze33.

Rule for printing the name of a room (called R) when R is not in Aboveground:
say “Maze”.

Check going down from the Kitchen:
say “(Down here, single-keystroke commands rule. Use the arrow keys or NSEW to move around; U or escape to quit.)”;
continue the action.

Check going up when the location is not in Aboveground:
say “You fumble your way back to the light.”;
now the player is in the Kitchen;
stop the action.

Prompt displaying rule when the location is not in Aboveground:
instead say “==>”.

Setting up input rule when the location is not in Aboveground:
now the input-request of the story-window is char-input;
rule succeeds.

Handling input rule when the location is not in Aboveground and handling char-event:
let C be the current input event character;
if C is special keycode left or C is Unicode Latin small letter w or C is Unicode Latin capital letter W:
say “(You try going west…)”;
handle the current input event as the action of going west;
continue the action;
if C is special keycode right or C is Unicode Latin small letter e or C is Unicode Latin capital letter E:
say “(You try going east…)”;
handle the current input event as the action of going east;
continue the action;
if C is special keycode up or C is Unicode Latin small letter n or C is Unicode Latin capital letter N:
say “(You try going north…)”;
handle the current input event as the action of going north;
continue the action;
if C is special keycode down or C is Unicode Latin small letter s or C is Unicode Latin capital letter S:
say “(You try going south…)”;
handle the current input event as the action of going south;
continue the action;
if C is special keycode escape or C is Unicode Latin small letter u or C is Unicode Latin capital letter U:
say “(You try going up…)”;
handle the current input event as the action of going up;
continue the action;
if C is special keycode escape or C is Unicode Latin small letter o or C is Unicode Latin capital letter O:
say “(You try undoing…)”;
follow the immediately undo rule;
say “(’[extended C]’ is not a valid key.)”;
reject the input event.

To decide what number is the undo value: (- UndoSave() -).

Include (-

[ UndoSave i;
i = VM_Save_Undo();
#ifdef PREVENT_UNDO; undo_flag = 0; #endif;
#ifndef PREVENT_UNDO; undo_flag = 2; #endif;
if (i == -1) undo_flag = 0;
if (i == 0) undo_flag = 1;
if (i == 2) {
VM_RestoreWindowColours();
VM_Style(SUBHEADER_VMSTY);
SL_Location(); print “^”;
! print (name) location, “^”;
VM_Style(NORMAL_VMSTY);
IMMEDIATELY_UNDO_RM(‘E’); new_line;
}
return i;
];

-).

Last handling input rule when handling char-event:
if the undo value is 2 begin;
reject the input event;
otherwise;
rule succeeds;
end if.[/code][/spoiler]

It’s a bit hacky, but it works!

Hope this helps.

In fact it never did work.

What I mean is: even in a totally normal parser game, you cannot get an undo effect by writing “follow the immediately undo rule” in an action rule. That’s after the game state has been saved, which, as you point out, is a mistake. (It doesn’t lock up the game, but it doesn’t rewind time either.)

The immediately undo rule is only for use by input-loop hackers.

I think we are in agreement but using two different definitions for “work”. It works in the sense that it does what it’s supposed to do and it doesn’t work in the sense that it doesn’t do what’s expected of it. The immediately undo rule is only properly used for the final question where standard parsing and undo saving is in the distant past and it only makes sense there. It might work in an after reading the command rule but that comes under input loop hacking.