I6: Hyperlinks in Glulx

I’m curious whether Glulx (and its interpreters) allows clickable text links to be used issue commands – in an I6 game, not I7. This is a standard feature in T3, for instance. You can make the descriptions of the room’s exits (“A path leads north.”) into hyperlinks that, when clicked, will issue a relevant command (‘go north’).

There’s an I6 extension called Markup.h, but I can’t quite tell whether it allows this functionality. The documentation on it says:

! By itself, this tag [the a href= tag] is not terribly useful. It becomes useful only when
! the game has code in it to listen for hyperlink events, in order to attach
! actions to the links.

That seems clear. But I can’t find a word in Markup.h about how one would add code to listen for hyperlink events and attach actions to them. So I’m left pretty much in the dark.

I do understand that not all interpreters will support links as a way to enter commands with the mouse. I also understand that Markup.h inconveniently forces one to number the href entities in the anchor tag. That’s all okay with me. I’m just wondering, how would I handle the mouse button event?

Or is there another I6/glulx extension that does this, perhaps in a more straightforward way?

Thanks!

–JA

In I6, you would write a HandleGlkEvent() function which catches event type 8.

Hmm. Doe has some information on her site about how to use this, but I’m too tired tonight to make sense of it.

It’s not entirely clear to me, for example, which of the words in the ev argument one would test to see if it’s an event of type 8. Nor is it clear to me how one would find the value of href= in the anchor tag. Nor is it clear how one would then generate an artificial command line input from within HandleGlkEvent.

But as I said, I’m too tired. I’ll take a closer look tomorrow. Thanks for the pointer.

–JA

Gull also has discussion, with examples:

adamcadre.ac/gull/index.html

You could probably reverse-engineer the I6 by looking at the Basic Hyperlinks I7 extension.

–Erik

Right now I’m looking at the Gull page on capabilities testing (adamcadre.ac/gull/gull-2c.html). That page offers this code:

if (glk_gestalt(gestalt_Graphics, 0)) {

…so I’m trying that in a simple test game, using Inform 6.31 (in Windows 7, if that matters). I’m using the -G flag, and I know the test game is compiling to Glulx, because I get an output file called MyGame1.ulx.

However, the compiler reports, "Error: No such constant as “glk_gestalt” and "Error: No such constant as “gestalt_Hyperlinks”.

I’ve pretty much ruled out a typo in my code. One possibility that remains is that the Gull page has obsolete information. Another possibility is that I need to set a constant somewhere in addition to using the -G switch.

I’ll keep poking at it, but if anyone has any suggestions, I’d appreciate hearing them!

–JA

Okay, got it. False alarm. Never mind. I needed to include Infglk.

Here’s a slightly more interesting question. I’m attempting to set up a text link so that when the player clicks the word “east” in the room description, the command “go east” (followed by a Return) will appear on the command line of the interpreter, with the expected result.

It’s not working yet.

The link appears correctly in the terp. Here’s the code that sets it up:

[code][link_set dir;
switch (dir) {
E_GOING: glk_set_hyperlink(E_GOING);
print “east”;
glk_set_hyperlink(0);
};
];

Room study “Your Study”
with description [;
print "There is a doorway to the “;
link_set(E_GOING);
" of this austere room.”;
],
e_to hallway;[/code]
Here’s how I’m trying (and failing) to handle the click on the hyperlink:

[code]Constant E_GOING = 1;
Constant W_GOING = 2;

[ HandleGlkEvent ev context abortres newcmd cmdlen;
switch (ev–>0) {
evtype_Hyperlink:
glk_request_hyperlink_event(gg_mainwin);
glk_cancel_line_event(gg_mainwin, 0);
if (ev–>2 == E_GOING) {
newcmd = “go east”;
cmdlen = PrintAnyToArray(abortres+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, newcmd);
abortres–>0 = cmdlen;
glk_set_style(style_Input);
print “go east”;
glk_set_style(style_Normal);
new_line;
}
}
];[/code]
What this does is, when I click on the link, the words “go east” are printed on the line BELOW the prompt, the input cursor remains on the line with the prompt, and input is dead. The game no longer accepts typing input.

I’m trying to use the explanation on the Gull pages. I think I’ve copied the code exactly. Adam says those lines are supposed to do the job. But they don’t. Since I can’t find a reference to PrintAnyToArray in the DM4, I have no idea what it’s doing. I’m stumped.

–JA

Now I can’t remember how it worked (not hacked with glulx low level stuff for quite a long time!), but just in case it helps, a couple of years ago we were looking for a way to type a verb, like “examine”, and then click on a link with an object and make the command “ex object” to be atomatically executed. As I’m reading that thread now, we were taking for granted that making a full command work from a link was actually the easy part!

It was here. Perhaps something in that thread helps.

Thanks, rockersuke. I looked at the thread, and tried a couple of things that it suggested, but I’m still getting the same results as before.

I’m not sure what you’re doing with this line:

for (i=4:buffer->i:i++);

Does that increment i until the value of buffer->i is zero? I think so. It wouldn’t help in my code, so I guess I should ignore it.

That’s right, that was for the specific subject in that thread.

Concerning your question, put the printing of the new prompt right before the glk_cancel_line_event() call and don’t forget to finish it all with a return 2. It’s working for me right now!

(well, almost, there’s an ugly side effect if player types anything in the prompt before clicking on the link, but dealing with that is far beyond my very rusty glulx knowledge! ^_^')

The printing of the new prompt? Sorry … what line causes that?

There goes a very basic implementation of it. It doesn’t even check the number of the link, just tries to execute “go n” when you click in the “NORTH” link of the room 01 description.

[code]!% -G
!% -D

Include “Parser”;
Include “Verblib”;
Include “Infglk”;

Object Room_01 “Room 01”
with
description
[;
print "You’re in Room 01. An exit leads ";
glk_set_hyperlink(1);
print “NORTH”;
glk_set_hyperlink(0);
print_ret “.”;
],
n_to Room_02,
has light
;

Object Room_02 “Room 02”
with
description “You’re in Room 2”,
s_to Room_01,
has light
;

[Initialise;
glk_request_hyperlink_event(gg_mainwin);
lookmode = 2;
location=Room_01;
];

[HandleGlkEvent ev context abortres newcmd cmdlen;
switch (ev–>0)
{
evtype_hyperlink:
glk_request_hyperlink_event(gg_mainwin);

		glk_set_style(style_Input);
		print "go north";
		glk_set_style(style_Normal);
		
		glk_cancel_line_event(gg_mainwin,0);
		newcmd="go n";
		cmdlen=PrintAnyToArray(abortres+WORDSIZE,INPUT_BUFFER_LEN-WORDSIZE,newcmd);
		abortres-->0=cmdlen;
		
		return 2;  

}
];

Include “Grammar”;[/code]

As mentioned, its only drawback is the mixing of the “go north” new command line with anything the player had typed before clicking (which would go unnoticed if she hasn’t typed anything, of course).

Thanks. That was very helpful. I seem to have it working now … almost. The only bit of piffle that remains is that when I click a link, the result looks like this:

I’m using a dummy object to print the command, and I’m sure that’s horribly wrong, but it’s the best I’ve been able to figure out. Here’s what I have at present:

[code]Object px “dummy”
with short_name “dummy”,
has proper
;

[ HandleGlkEvent ev context abortres newcmd cmdlen;
! To suppress a compiler warning:
context = 0;
switch (ev–>0) {
evtype_Hyperlink:
glk_request_hyperlink_event(gg_mainwin);
glk_cancel_line_event(gg_mainwin, 0);
switch (ev–>2) {
e_obj: newcmd = “go east”; px.short_name = “go east”;
w_obj: newcmd = “go west”; px.short_name = “go west”;
axe: newcmd = “x axe”; px.short_name = “x axe”;
}
glk_set_style(style_Input);
print (The) px;
glk_set_style(style_Normal);
new_line;
cmdlen = PrintAnyToArray(abortres+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, newcmd);
abortres–>0 = cmdlen;
};
return 2;
];[/code]

If I don’t use the four lines before cmdlen = PrintAnyToArray, the new command is never printed at all, even though it’s carried out. So evidently I need to get rid of that kluge and figure out a way to stuff the actual command on the line with the command prompt. I have no clue where to look for a way to do that.

Adam Cadre, very unhelpfully, says this: “Now, you can either place that very phrase at the prompt to let the player know that’s what’s been done, or you can do something like this…” He then gives, more or less, the four lines that are printing my output in the wrong place.

<Sigh.>

No sooner do I solve that problem than I find I have a worse problem. When the player (at the moment, player = me) resizes the game window, a GlkEvent is generated, which means that HandleGlkEvent is called. This has the effect of repeating the last command, whatever it was – and whether or not it resulted from clicking a link. I have no smegging idea how to prevent this, because nothing in my HandleGlkEvent code seems to be responsible for it. Here’s what I have:

[code][ HandleGlkEvent ev context abortres newcmd cmdlen;
switch (ev–>0) {
evtype_Hyperlink:
glk_request_hyperlink_event(gg_mainwin);
switch (ev–>2) {
e_obj: newcmd = “go east”; px.short_name = “go east”;
w_obj: newcmd = “go west”; px.short_name = “go west”;
axe: newcmd = “x axe”; px.short_name = “x axe”;
}
glk_set_style(style_Input);
! print the command, whose text is now stored as the short_name of dummy object px:
print (The) px;
glk_set_style(style_Normal);
new_line;

		cmdlen = PrintAnyToArray(abortres+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, newcmd);
		abortres-->0 = cmdlen;
	default:
		context = 0;
	};
	return 2;

];[/code]

One would naively expect that a window resize event would fall through to the default case, which would do nothing. But no … a new command is silently issued in the background.

Are evtype_Hyperlink and switch (ev–>2) supposed to be indented to the same level like that?

You can change this behavior by taking advantage of one of the new Glk calls: glk_set_echo_line_event.

It lets you turn off the automatic printing of the player’s input - but you are then responsible for printing something to the screen in its place. (You might also need to update your copy of infglk.h, depending on how recent it is.)

Indentation is not significant in I6.

As I told you before, you must put the lines that write the new command at the prompt before calling the glk cancel line function, that’s to say:

this

glk_set_style(style_Input);
print "MY NEW COMMAND HERE!";
glk_set_style(style_Normal);

goes before this:

glk_cancel_line_event(gg_mainwin,0);

And as I mentioned, the ugly side effect is that if the player types something before clicking the result could be like this (in the example she has typed “xyzzy”, then, for any reason, changed her mind and clicked on the “NORTH” link)

I’m going to take a look to this glk set echo calls (I didn’t know they existed! nice!) to see if they can help with this.

You’re doing the “return 2” from the HandleGlkEvent main routine. You should put it in the evtype_hyperlink: case of the switch (ev–>0) switch. If not, you’re returning “2” wichever the event is, which has the effect of canceling the current command line. And I miss the glk_cancel_line_event() call in your last code, which surely is causing some more adiotional chaos ^_^'.

No, that’s not legal. You can’t print* when there’s a line event pending, so the cancelation needs to happen first. The only way to prevent the line break is to use the set echo function that Ben mentioned.

–Erik

*Some interpreters will print text to the window while a line event is pending, but it is unambiguously out-of-spec.

Thanks to everyone for offering suggestions. My reasons for wanting to use I6 were (a) I dislike I7, but (b) I want my game to be playable in a Web browser.

But the underlying impulse behind (b) is, I want the player to have a pleasant, modern experience. My foray into Glulx programming for I6 has convinced me that I’m unlikely to be able to provide that experience using these tools – not without becoming so frustrated that I’ll lose all interest in the project. The I6/glulx documentation is bad to nonexistent, and the coding requirements seem to be pretty much a mess.

At this point, it seems clear to me that using TADS 3 will be a much better alternative. Even if MJR doesn’t have his Web server implementation up and running any time soon, the other aspects of the user experience will be more comprehensive, more reliable, and a whole lot easier to code. On balance, I6 loses; T3 wins.

Too bad. I like I6, and I was sort of looking forward to becoming an I6 virtuoso. YCAGWYW, but if you try some time, you just might find…

–JA