Abbreviating a direction to one letter: I6-ish way to do so?

I’m writing something in Inform 7 and have a solution but it feels like something people should be able to do easily in Inform 6, if they know it well enough.

For Inform 7, we can say

A direction has text called abbr.
abbr of north is "n".
abbr of south is "s".
abbr of east is "e".

And so forth. This works well. But we can also do something more computational.

to say abbr of (di - a direction): say "[character number 1 of the printed name of di]"

This is pretty tidy Inform 7 code (as long as I’m not worried about diagonal directions!) but I noticed it takes a decent bit of z-machine space. Not too much, but enough for me to say – can this be done in I6? (I’ve looked at the auto.inf generated, and it makes a certain amount of sense, but it seems like it can be more succinct.)

Thanks!

Not very easily. Manipulating individual characters in a string is something the Z-machine just isn’t very good at, because strings are stored in a special compressed format (which also serves to make it hard to find the game text with a hex editor—important back in Infocom’s era!). You can do it, but it’s much slower and clunkier than you’d expect.

And as a result of this, Glulx isn’t great at it either—since the Z-machine was bad at it, it wasn’t a high priority for Glulx, and was sacrificed in favor of better text compression. On both platforms, this sort of string manipulation requires printing the text into a separate buffer and then manipulating that, rather than just being able to say obj.name[0] like you could in C.

The standard I6 way to do this, since the number of directions is pretty small, is to just make a table of 'em. Either do it by hand, or with a separate program, but either way, it’ll generally be faster and more efficient to do it beforehand.

(Of course, the inefficiency isn’t an issue on modern systems. We’ve got games like Counterfeit Monkey that do this sort of string manipulation all the time and it’s no big deal, even in a web browser. But it sounds like it is something that matters for your use case, and if so, going down to I6 won’t really help.)

1 Like

(Moved this to the I7 forum because it’s about I6 in the context of I7.)

In the context of default I7 direction definitions, the abbreviation is always the third dictionary word in the .name array, so:

To say abbreviated name of (D - direction):
	(- print (address) {D}.&name-->2; -).

When play begins:
	repeat with D running through directions:
		say "[D] -> [abbreviated name of D][line break]".
3 Likes

Wow, this code is neat! For some reason I had to change the 2 to 1, and also, custom directions don’t seem to get an initial. There’s an obvious easy workaround for this in my case, since the directions are h, i, j and k.

But I couldn’t find where things were defined in the source e.g. north->n, northeast-ne, etc. I tried grep -i "\bne\b" *.i6t in the reserve directory. So I had a small patch.

definition: a direction (called d) is custom:
    if d is h or d is i or d is j or d is k, yes;
    no;

When play begins:
		repeat with D running through directions:
			say "[D] -> [if D is custom][D][else][abbreviated name of D][end if][line break]".

So, more generally, if we take the hubwards and rimwards example from the IDE docs, how would we cut things down? My brute force attempt fails but I hope it provides a clue what I am trying for. I suspect I’m missing something silly.

hubwards is a direction.
rimwards is a direction.
opposite of rimwards is hubwards.
opposite of hubwards is rimwards.

Include (-
	(+hubwards+).&name-->1 = 'h';
	(+rimwards+).&name-->1 = 'wi';
-).

Interesting- which version are you using? For at least Ver 9.1 onward, each built-in direction is compiled with three names- e.g. ‘north’, ‘direction’ and ‘n’ in that order, so that north.&name–>0 is ‘north’, north.&name–>1 is ‘direction’ and north.&name–>2 is ‘n’.

‘direction’ is a truncation of ‘directions’, this being the automatically-added plural kind name for the direction kind given to all direction objects -but truncated to the standard 9-character parser resolution.

Compiling the following should get you a list of all the names allocated to directions in your project:

To say names of (o - object): (- ListObjectNames ({o}); -).

Include (-	
[ ListObjectNames o x;
	for (x = 0: x < o.#name/WORDSIZE:  x++) print "  ", (address) o.&name-->x,"^";
];
-).

When play begins:
	say "[bold type]Direction Names[roman type][line break]";
	repeat with D running through directions:
		say "[D]:[line break]";
		say names of D;

When I compile this with any version from 9.1 to 10.1 I get:

Summary
Direction Names
north:
  north
  direction
  n
northeast:
  northeast
  direction
  ne
northwest:
  northwest
  direction
  nw
south:
  south
  direction
  s
southeast:
  southeast
  direction
  se
southwest:
  southwest
  direction
  sw
east:
  east
  direction
  e
west:
  west
  direction
  w
up:
  up
  direction
  u
down:
  down
  direction
  d
inside:
  inside
  direction
  in
outside:
  outside
  direction
  out
1 Like

Irritatingly, for directions you define yourself the names you give will be allocated first, in order, then the plural kind name.

So after

hubwards h is a direction.
rimwards wi is a direction.

you get

hubwards h:
  hubwards
  h
  direction
rimwards wi:
  rimwards
  wi
  direction

to keep everything in line with how the built-in directions are named (at least from Ver 9.1 onwards) you need to swap over the 2nd and 3rd names for each of these directions before play begins:

To swap last 2 names of (o - object): (- SwapLast2Names({o}); -).

Include (-
[ SwapLast2Names o n x;
	n = o.#name/WORDSIZE;
	if (n>1) {
		n--;
		x=o.&name-->n;
		o.&name-->n = o.&name-->(n-1);
		o.&name-->(n-1) =x;
	}
];
-).
First when play begins:
	swap last 2 names of hubwards;
	swap last 2 names of rimwards;

EDIT: it now occurs to me that instead of all this mad science, since extra individual names allocated through ‘Understand…’ phrases are appended to the ‘name’ property array after automatically-generated names, all you need to do is the following:

hubwards is a direction.
rimwards is a direction.

Understand "h" as hubwards.
Understand "wi" as rimwards.

and you get

hubwards:
  hubwards
  direction
  h
rimwards:
  rimwards
  direction
  wi
2 Likes

Thanks! This seems like the most universal fix, as it works in a lot of versions of Inform 7, including the old one I still use. It feels a bit like magic, but as usual, it’s neat to compare the auto.inf of this with

hubwards is a direction.
rimwards is a direction.

to see the differences.

It makes total sense now I see the syntax, but I guess I wasn’t expecting anything that slick.

The most universal fix is your original suggestion:

A direction has text called abbr.
abbr of north is "n".
[etc]

This works in all versions of Inform without worrying about it. Not having to worry about it is the highest virtue.

What this thread has settled on is the most universal I6 hack. :)

1 Like

Returning to the original suggestion of using I6 to programmatically extract the initial character of a direction name in minimalist fashion, here’s a totally gratuitous hackfest (almost) guaranteed to work in all Inform versions. It might not work so well for the Z-machine if you’ve somehow managed to compile a custom alphabet table or redefined ZSCII codes. It’s tested back to Ver. 9.1, but as it uses only basic techniques, it should work on even earlier versions.

Glulx makes extracting the first character from a dictionary word trivial- it’s one line of I6 code.

The way the Z-machine encodes/compresses its dictionary, not so much. Two methods are presented-
(i) the super-hacky equivalent to the Glulx code, which extracts and decodes a limited range of simple first characters direct from the dictionary
(ii) the somewhat-less-hacky and more versatile version, which uses the Z-machine’s printing routines to decode the dictionary word and print the resulting text to an array, from which the first character is then printed.

EDIT 1: (i) hackified further so that it decodes and prints the full range of standard printing ZSCII characters, to include more punctuation/symbols and also the international and accented characters in the ASCII/ZSCII range155-223. So should now be functionally equivalent to (ii).

EDIT 2: if you’re only ever going to start your object names with alphabetic characters, you can reduce (i) to the much more streamlined:

Include (-
[ GetInitialOf o f;
f = ((((o.&name-->0)->0) & 124)/4);     ! first encoded character
if (f>5) print (char) f+91;                                 ! unshifted alphabetic character
else print (char) 64;                                         ! error code
];
-).
The Textual Abuse Laboratory is a Room.

Section Z-Initial (for Z-machine only)

[this extracts and decodes the first encoded character from the dictionary word pointed to by the first entry in the name property array, then prints it.
This simple decoding algorithm only works for plain alphanumeric letters a-z /0-9 and a limited range of punctuation characters, as defined in Array Z_table_A2.
Any other character is represented by printing a '@' character as an error code.]
Include (-
Array Z_table_A2 -> 64 64 64 64 64 64 64 64 '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' '.' ',' '!' '?' '_' '#' 39 34 '/' '\' '-' ':' '(' ')' ;
-).

Include (-
[ GetInitialOf o f s ;
f = ((((o.&name-->0)->0) & 124)/4);     ! first encoded character
s = ((((o.&name-->0)->0) & 3)*8)+((((o.&name-->0)->1) & 224)/32); ! second encoded character
if (f>5) print (char) f+91;                                 ! unshifted alphabetic character
! else if ((f==4) && (s>5)) print (char) s+59;  ! shifted alphabetic character -> capital letters A-Z- unused in the dictionary with standard alphabet table
else if (f==5) {
	if (s==6) {                                                  ! next two 5-bit characters are a 10-bit ZSCII code, hi-bits then lo-bits
		f = ((((o.&name-->0)->1) & 31)*32);          ! hi-bits
		s = ((((o.&name-->0)->2) & 124)/4);          ! lo-bits
		print (char) f+s;
	}
	else print (char) Z_table_A2->s;               ! double-shifted character -> numerals and punctuation (according to Array defined above)
}
else print (char) 64;                                         ! error code
];
-).


Section - Z1-Initial (for Z-machine only)

[this uses the printing to array method to get the text of the first entry in the name property array, enabling a wider range of international chracters to be extracted]
Include (-
Array printed_text->15;
-).

Include (-
[ GetFirstLetterOfName o;
	@output_stream -1;                             !turn off printing to screen
	@output_stream 3 printed_text;          !turn on printing to array
	print (address) o.&name-->0;              !print first entry of name property to array
	@output_stream -3;                             !turn off printing to array
	@output_stream 1;                              !turn on printing to screen
	print (char) printed_text->2;                !printed_text -->0 is the number of words printed, so printed_text->2 is the first ZSCII character of the name
];
-).


Section G-Initial (for Glulx only)

[this simply prints the first character from the dictionary word pointed to by the first entry in the name property array of the given object]
Include (-
[ GetInitialOf o ;
print (char) ((((o.&name-->0)->1)));     ! first character
];
-).

Section - Testing

To say the/-- initial of (o - an object): (- GetInitialOf({o}); -).

\veer-right is a direction. [the following should all be included in the basic z-machine alphabet tabel]
/veer-left is a direction.
? is a direction.
90_degrees is a direction.
#3 is a direction.
Paris-wards is a direction. [this will be transformed to lower-case in the dictionary]
-> is a direction.                [hyphen included in basic z-machine alphabet table]
<- is a direction.                [less-than not included in basic z-machine alphabet table]
Östlich is a direction.         [capital o-umlaut not included in basic z-machine alphabet table and not transformed to lower case in the dictionary]
östlich is a direction.          [lower-case o-umlaut not included in basic z-machine alphabet table]

To say names of (o - object): (- ListObjectNames ({o}); -).

Include (-	
[ ListObjectNames o x;
	for (x = 0: x < o.#name/WORDSIZE:  x++) print "  ", (address) o.&name-->x,"^";
];
-).


When play begins:
	say "[bold type]Direction Names[roman type][line break]";
	repeat with D running through directions:
		say "[D]:[line break]";
		say names of D;	


Section G-Test (for Glulx only)
	
When play begins:
	repeat with d running through directions:
		say "The initial letter of [d] is '[initial of d]'[line break]";
		
Section Z-Test (for Z-machine only)

To say the/-- first letter of (o - an object): (- GetFirstLetterOfName({o}); -).

When play begins:
	repeat with d running through directions:
		say "The initial letter of [d] is '[initial of d]' (extracted) or '[first letter of d]' (printed)[line break]";
1 Like