Bubbling Beaker Awards (Award #23, Apr 19 2024)

The good doctor has suggested that I explain a bit about the granular details of the SnippetSoFar() routine on which the solution depends.

The first thing to understand is the nature of a snippet. Although outlined briefly in WWI 18.33, a clearer definition is provided in commentary found within the template files, which begins:

Although the idea is arguably implicit in I6, the formal concept of “snippet” is new in I7. A snippet is a value which represents a word range in the command most recently typed by the player.

As explained in that section, a snippet is encoded as S*100+L, where S is the starting word number (with 1 representing the first word), and L is the total number of words. So for the command >ATTACK TROLL WITH ELVISH SWORD, the player's command (a snippet) would be encoded as 105, i.e. five words total, starting with the first word. The snippet for words matching the second noun (the elvish sword) would be encoded as 402. The default value of a snippet is 100 (instead of zero), and in template code this is the value expected for an empty snippet.

The second thing to understand is the pair of global variables of interest for this problem: wn and match_from. These are two of several variables used to store parser state. The variable wn (“word number”) is used as a kind of pointer during the parsing process, keeping track of the next word of command input to be examined. The variable match_from serves as a kind of bookmark that is set when the process of matching words to objects begins; it stays the same throughout a given matching attempt.

The SnippetSoFar() routine is straightforward:

[ SnippetSoFar ;
	if (wn == match_from)							! if no words have been matched yet...
		return 100;									! ... return the empty snippet
	else											! ... otherwise ...
		return (match_from*100)+(wn-match_from);	! ... return a snippet encoding the noun words processed so far
];

The routine is written for use as part of the condition of an Understand ... when ... statement. When considering such a line, I7 evaluates the condition before attempting to match any words. As an example, given an I7 statement like

Understand "light" as a light when the snippet-so-far does not include "light".

the generated I6 parse_name routine that implements that understanding will involve a block that includes

...
if (Cond_Token_165() == GPR_FAIL) jump Fail_1;	! fails if snippet-so-far includes 'light'
if (NextWordStopped() ~= 'light') jump Fail_1;	! fails if next word evaluated is not 'light`
...

where Cond_Token_165() is a routine that will check the truth value of the condition, using logic that calls on SnippetSoFar(). If the Cond_Token routine signals failure, then the program flow moves out of this block and on to the logic for any other applicable Understand... lines. If the Cond_Token routine does not signal failure, then the next line checks whether or not the “next word” (i.e. word at position wn) is 'light'.

Whenever the Cond_Token routine is being executed wn-1 will point to the last word evaluated. Since progress in noun parsing is measured in terms of the number of words evaluated without failure since starting to match words at position match_from, it follows that whenever wn is at least one more than match_from, the formula (match_from*100)+(wn-match_from) generates a snippet meaning “the one or more words so far matched to this object.”

5 Likes

This week’s prize, Bubbling Beaker Award® #18 is presented to @matt_weiner, who has not visited the forum for quite a while but seems to have had the mad scientist spirit in spades. His award-winning post (which doesn’t use I6) demonstrates a method for establishing mutually-exclusive sets of relations, in an example concerning attitudes between people ranging from loving to hating.

The interesting part of this approach is that it is another way of approximating trinary relations. It’s similar in setup and usage to how I7 handles the relationship between origin room, destination room and direction via the various mapping <direction> relations, and similar in function to I7’s special handling of the various mutually-exclusive “spatial” relations (containment, support, carrying, etc.). When any one type of relation is set between two people using the provided set the feeling... phrase, any other type of relation between the two people is cleared.

4 Likes

I’d be inclined to go with:

Attitude is a kind of value. Attitudes are indifference, hate, dislike, like, love.

A person has a relation of person to attitude called the disposition.

To decide what attitude is the feeling of a/an/-- (p - person) toward/towards a/an/-- (q - person):
  if q relates to an attitude by the disposition of p, decide on the attitude that q relates to by the disposition of p;
  else decide on indifference.

To set the feeling of a/an/-- (p - person) toward/towards a/an/-- (q - person) to a/an/-- (att - attitude):
  now the disposition of p relates q to att.

Lab is a room. Alice is a person. Bob is a person.

when play begins:
  set the feeling of alice toward bob to love;
  set the feeling of bob toward alice to like;
  set the feeling of player toward bob to dislike;
  repeat with p running through people begin;
    repeat with q running through people who are not p begin;
      say "[p] feels [feeling of p toward q] toward [q].";
    end repeat;
  end repeat;

But, then, I would say that, wouldn’t I.

1 Like

I’m ashamed to say that I’ve only just had a proper look at this award, and think it deserves a little more explanation. The fundamental problem to be solved is another facet of an issue that was the basis of a recent post discussion and Bubbling Beaker award #1- the constraints on the kinds of property Inform currently allows things to be ‘Understood’ by. (see Writing with Inform §17.15. Understanding things by their properties)

When understanding something by one of its properties, Inform permits that property to be one of a limited variety of kinds (although much less limited than suggested by the compiler error generated by trying to stray beyond this list):

either/or property (e.g. a thing can be broken or unbroken)
truth state (e.g. a secret door has a truth state called password-guessed)
enumerated kind of value (e.g. Colour is a kind of value. Some colours are red, green, blue. A thing has a colour called hue)
scene (e.g. A thing has a scene called prop-requirement)
command parser error (e.g. A thing has a command parser error called error-type.)

number (e.g. a treasure has a number called points-value)
time of day (e.g. an animal has a time of day called feeding-time)
real number (e.g. a thing has a real number called calculated-value)
unit (e.g. A weight is a kind of value. 10kg specifies a weight. A thing has a weight called encumbrance.)

text (e.g. A thing has a text called abbreviated-name)

figure name
sound name
external file

natural language
narrative viewpoint
grammatical tense
grammatical case
grammatical gender

are permitted kinds, but not

topic
snippet
unicode character
rulebook
rulebook outcome
action
action name
response
verb
table name
equation name
list of …
phrase …

object or subkind of object (e.g. thing)

In the Kinds Chart of the Index, this almost corresponds to the kinds which can appear in Understand tokens (e.g. “[colour]”) with the exceptions that:

(i) the Understand token “[text]” refers to a topic, not a text, and whereas topic properties cannot be understood as their objects, text properties can
(ii) subkinds of object can appear in Understand tokens such as “[person]” but properties holding subkinds of object cannot be understood as their objects

The challenge laid down was for an object to be understood by a property of one of its properties. The specific context was the wish to concisely define a list of material types (iron, wood etc.) in a table alongside properties of those material types- e.g.

A material is a kind of thing.
The materials are defined by the Table of Material-characteristics.

Table of Material-characteristics
material	material-adjective	destroying heat	heat-behaviour	corrosion resistance
iron	    “iron”	            10	            melter	        995
wood     	“wooden”          	3	            burner         	970
adamant 	“adamantine”	    999	            melter          1000

after which, there would be three objects of material kind called iron, wood and admanant and for example the material wood would have the material-adjective property “wooden” (a text property).

The idea was then to have:

A thing has a material called essence. Understand "thing/object" as a thing.
A staff is here. The essence of the staff is wood.
A rod is here. The essence of the rod is wood.
A sword is here. The essence of the sword is iron.

The author wanted the player to be able to type “take every wooden thing” in order to pick up just the nearby things made of wood, using the material-adjective property as laid out in the table- i.e. the material-adjective property of the essence property. e.g.

Understand the material-adjective property as describing a material. [so that "wooden" describes the wood object]
Understand the essence property as describing a thing. [so that, for example, the wood object in turn describes a thing holding a wood object in its essence property]

Now, here’s the snag. As noted above, a thing cannot in the ordinary way of things be understood by a property that itself holds a subkind of object (in this case an object of material kind). So the second Understand phrase won’t compile, being spat out with the following complaint:

You wrote ‘Understand the essence property as describing a thing’ : but the value of that property is itself a kind of object, so that it cannot be understand as describing or referring to something.

We could make materials an enumerated kind of value rather than a kind of thing (A material is a kind of value. The materials are defined by by the Table of Material-characteristics.), in which case the second Understand phrase will compile fine (as noted above, properties holding enumerated values are fine to be understood as their objects) but now the first won’t compile, since only objects can be understood by their properties- and now materials are no longer a kind of object, but a kind of value.

The award-winning solution is to make the essence property understood as its object not by the usual (disallowed)Understand the ... property as describing a ... method, but by the Understand "[something related by ...]" as a ... method. ( see Writing with Inform §17.16. Understanding things by their relations)

For this to work, it’s necessary to set up a simple ‘conditional’ relation between a thing and whatever object is in its essence property (see Writing with Inform §13.12. Relations which express conditions):

Constitution relates a thing (called the item) to a material (called the constituent) when the essence of the item is the constituent.

and now:

Understand "[something related by constitution]" as a thing.

(where [something related by constitution] now means ‘whatever object is held in a given thing’s essence property’)
and so overall this means the parser can now recognise a given thing by any word(s) by which it can recognise the object in that given thing’s essence property, which will include the name(s) of that object (e.g. ‘wood’) but also any properties describing it (e.g. “wooden”).

So, essentially the solution is a neat workaround for Inform’s refusal to allow an object/object kind property to describe its object.

Unfortunately this particularly neat trick doesn’t allow a similar workaround for the other varieties of property listed above that are presently disallowed from describing their objects, because at present Inform only allows relations between kinds of object to appear in Understand phrases.

3 Likes

It’s not too hard to extend the technique to any sayable value.

Lab is a room.

an action-nom is a kind of object.
an action-nom has an action name called the action-name.
an action-nom has a text called description.
The description of an action-nom is usually "[actionless action of the action-name of the item described]".
Understand the description property as describing an action-nom.

to say actionless action of/for (ac - action name):
  let t be "[ac]";
  replace the text " action$" in t with "";
  say t;

Jumping-obj is an action-nom with action-name jumping action.

describing is an action applying to one visible thing.
understand "describe [any action-nom]" as describing.
report describing: say the action-name of the noun; say " action".

Proxying relates one thing to one action-nom.
the verb to proxy means the proxying relation.
the jumprope proxies jumping-obj.
the jumprope is in the lab.
understand "[something related by proxying]" as a thing.

test me with "describe jumping / get jumping / i".
3 Likes

This week’s prize, Bubbling Beaker Award® #19 is presented to @drpeterbatesuk. Continuing his efforts to get better use out of the text matching functionality of topics, he shows how to fool the compiler into accepting the logical equivalent of a variable holding a topic, which makes it easy to change out the topic used in a particular comparison. (This technique is vaguely similar to his invention from award #1, but the details of the use case look significantly different to me, and the implementation is more readily understood by the budding mad scientist.)

This marks the good doctor’s fourth BBA. Congratulations! In recognition of this achievement, I’ve added a leaderboard to the top post.

2 Likes

But this is an exhibition, not a competition: please, no wagering.

3 Likes

If I have seen further, it is by standing on the shoulders of giants.

                                  Isaac Newton 1675

Wow, well I feel a bit of a fraud with this one- because if ever there was a case of standing on the shoulders of giants, this was it.

The unlikely background to this minor advance was a simple enquiry as to why the following:

if the player's command is "[Password]":
...

(where Password is a text variable)
doesn’t work.

This led to a whole cascade of stuff mostly outside the original thread-

  1. the incidentally-noted fact that a (supposedly) random choice from 4 alternative passwords turned out to be very much NOT random- caused by a glitch with the Glulxe random number generator that I had noted previously but shelved looking into in detail, which led to…
  2. a very long post with multiple distinguished contributors culminating in revision of the Glulxe and Git random number generators…
  3. and which itself led to my stumbling on a very sneaky means to smuggle raw I6 code into the final compiled output of I7 when using Version 10- the subject of Bubbling Beaker Award #7

all of which is a perfect illustration of how even the simplest of enquiries on this forum can lead to a whole heap of good stuff of interest and value to even the most experienced denizens and contributors.

A few words now- after that preamble- on this fortnight’s award.

First to recap some basics. I7 has a closely-patrolled typing system whereby if a phrase expects, for example, a text, Inform complains and refuses to proceed if you try to feed it instead with something different, say a number. I7 however compiles to I6, a much more primitive language, which has only a rudimentary typing system and one that is not enforced. Fundamentally, to I6, entities of any of I7’s types are just integer numbers, which is how they are represented in I6.

So a text is represented by an integer number. A phrase is represented by an integer number. A snippet is represented by an integer number. And, relevant to the present issue, a topic is represented by an integer number.

How these various numbers are interpreted by I6 varies from type to type.

An I7 text is represented by a number which is a memory address where data is stored describing exactly what flavour of text this is and from that, both where and how to retrieve and assemble the sequence of characters which ultimately make up the text.

An I7 snippet is represented by an integer number which simply encodes the starting position and length of a sequence of words in the player’s command.

A topic is represented by an integer number which is the memory address of a routine. Specifically, for a topic this routine will be something called a General Parsing Routine which, on a basic level, tries to match certain dictionary words against the contents of a given snippet.

In I7, the boundaries between types (or kinds, as I7 likes to term them) are largely defended- with few exceptions you usually can’t try to turn something of one type into another (a process known as casting) without Inform complaining. But because I7 allows you to link an I7 entity such as a text to an I6 equivalent value- which will always be an integer number- and vice versa, it is possible via I6 to persuade I7 to cast between types (or kinds). To do this we simply find the integer number in I6 which represents the I7 entity of kind J we are casting from, then reverse the process to find what I7 entity of kind K that I6 integer number would be.

Long ago @Zed wrote a simple line of code which neatly performs this magic for any I7 entity and kind, which I shamelessly stole:

To decide which K is (v - a value) as a/an/-- (name of kind of value K): (- {v} -).

It’s easier to see how this works if we write a similar ‘To… phrase’ with a kind and a value explicitly stated:

To decide which number is the player's command as a number: (- (+ the player's command +) -).

which clearly means ‘decide on whichever number is the I7 number equivalent to the I6 representation of the player’s command’. (the I6 representation of the I7 snippet variable ‘the player’s command’ being an integer encoding, specifically 100 + the number of words in the command).

Now we can, for example, find the memory address (an integer number) where the metadata for an I7 text is stored by SomeText as a number or the integer number which encodes the player’s command (a snippet) by the player's command as a number. Casting by this method is often not very useful, because the I6 representation of one I7 kind as a number makes no sense as a representation of a different I7 kind. For example, casting a snippet to a text by this method won’t work, because the number encoding a snippet is not a memory address for text metadata, which is the meaning of an I6 number representing an I7 text- and this is why I7 normally steps in to prevent us trying to do something so foolish- in the jargon, I7 ‘enforces type safety’.So we need to be careful how we use this newfound power by ensuring we are only making casts that make sense.

I7 is a little ambivalent about topics, in that it is quite choosy about what it will allow us to declare as a topic. For example, an object can have a property that is a topic:

A thing has a topic called adjective. The adjective of the knife is "sharp/keen/steel"

Now we can write things like:

After reading a command:
	if the player's command includes the the adjective of the knife:
	...

and this works fine for assertions that set up the state of the world, but if we want to change the adjective property of the knife:

now the adjective of the knife is "blunt/steel";

we run into a problem, because in ‘now phrases’ the compiler prefers to think that “blunt/steel” is a text, rather than a topic, and we get the cryptic compiler error:

Problem. In the sentence ‘now the adjective of the knife is “blunt/steel”’, it looks as if you intend ‘the adjective of the knife is “blunt/steel”’ to be a condition, but though they look the same, because both are written in double quotes, text values can’t in fact be used as topics, so it’s impossible to store this piece of text in that location.

What we need is a means to nudge the compiler into treating “blunt/steel” as a topic rather than as a text, and @zarf provided the surprisingly simple means to do so:

To decide which topic is the topic (T - a topic): decide on T.

and now we can write:

now the adjective of the knife is the topic "blunt/steel";

and the compiler will happily accept “blunt/steel” as a topic rather than a text.

So now we can assert (during setup) and assign/reassign (during play) the contents of a topic property.

However, if instead of declaring a topic property we try to declare a topic variable Inform once again complains:

Blasphemy is a topic that varies.

Problem. You wrote ‘Blasphemy is a topic that varies’: but ‘topics that vary’ are not allowed, that is, a variable is not allowed to have ‘topic’ as its kind of value. (This would cause too much ambiguity with text variables, whose values look exactly the same.)

The workaround that is the basis of this Bubbling Beaker Award recognises that, as we’ve seen already, to I6 a topic is simply an integer number representing the memory address to be called in order to invoke a General Parsing Routine. And I7 is perfectly happy to hold integer numbers in a variable.

Blasphemy is a number that varies.

So now all we have to do is, whenever we want to assign a topic to Blasphemy, make sure to cast it to a number:

When play begins:
	now Blasphemy is the topic "by/-- Baal/Baal's/Astarte/Astarte's/Ilumquh/Ilumquh's and/-- all/-- his/her/-- devils/--" as a number.

and in reverse, when we want to use Blasphemy as a topic, cast it back from a number to a topic:

if the player's command includes Blasphemy as a topic:
...

and we can also assign to a topic property:

A person has a topic called source of damnation.
After reading a command:
	if the player's command includes Blasphemy as a topic:
		say "You are damned!";
		now the source of damnation of the player is Blasphemy as a topic;

or create and manipulate dynamic lists of pseudo-topics-as-numbers:

The Scroll of Blasphemies is a list of numbers that varies.
After reading a command:
	if the player's command includes Blasphemy as a topic:
		say "You are damned!";
		add Blasphemy to The Scroll of Blasphemies;

etc.

4 Likes

This is absolutely another “on shoulders of giants thing”. Ron Newcomb, October, 2010:

Though I never paid much attention to the “that might be going a bit overboard” part.

2 Likes

As a bonus for this week’s Bubbling Beaker Award™, here’s a reworking of the problem central to BBA #14, but instead using type casting.

To recap, the problem was to represent an I7 grammar token in an included segment of I6 code- the difficulty being that I7 provides no native means to translate an I7 grammar token to a corresponding general parsing routine (GPR) of known name in I6.

The opposite is provided for, whereby a GPR we have written to be a grammar token in I6 can be represented by a name we invent for it in I7- by The Understand token <I7-name> translates into I6 as <I6-name>.

The syntax of this looks as though the following might work:

Color is a kind of value. Some colors are red, green and blue.
Painting it with is an action applying to one thing and one color.
Understand "paint [something] with [color]" as painting it with.

The Understand token color translates into I6 as "color_token".

but it doesn’t- the translation has to go the other way:

Include (-
[ color_token;
    ... clever I6 code here...
];
-).

The Understand token color-token translates into I6 as "color_token".
Understand "paint [something] with [color-token]" as painting it with.

So we need a different way of representing the I7 [color] token (representing the name of any color, typed in a command) in I6. The (+ color +) or (+ [color] +) notation can’t be used either here.

BBA #14 approached the issue by extracting the compiled address of the I7 [color] token from the grammar table of a verb making use of it.

But a simpler approach is to use the trick of casting a topic to a number, remembering that a topic is essentially a general parsing routine- the only difference is that a topic expects to be called with the word number (in the player’s command) to start from given as a parameter, whereas a standard general parsing routine assumes without any parameter that matching should start from the parser’s I6 global variable wn, which tracks where the parser is up to in parsing the command.

So, after

default-color-token is a number that varies.

To set color token:
	now default-color-token is the topic "[color]" as a number.

‘default-colour-token’ is the address of an I6 routine which, if called from I6 with <start-word-number> as a parameter, will itself do nothing other than set wn to be <start-word-number>, then call ParseToken(GPR_TT, <color-token>) (where <color-token> is the compiled address of the I7 [color] token) and finally return whatever the [color] GPR returns, Effectively, our topic, represented by default-color-token, is a simple wrapper function to the [color] token.

So our I6 routine for a second color token, which was

[ second_color_token x firstnum;
	firstnum = parsed_number; ! saves the first parsed number locally
	x = ParseToken(GPR_TT, default_color_token); ! <- global assigned when play begins with the address of the [color] token GPR routine
	if (x == GPR_FAIL or GPR_REPARSE) return x;
	second_color = parsed_number; ! saves the second parsed number into a global
	parsed_number = firstnum; ! returns the first number to its usual global spot
	return GPR_PREPOSITION;
];

now becomes

[ second_color_token x firstnum;
	firstnum = parsed_number; ! saves the first parsed number locally
	x =  (+ default-color-token +)(wn); ! <- I7 representation of address of topic calling [color] token GPR routine, called with wn as parameter since we do want to parse starting from where we're up to
	if (x == GPR_FAIL or GPR_REPARSE) return x;
	second_color = parsed_number; ! saves the second parsed number into a global
	parsed_number = firstnum; ! returns the first number to its usual global spot
	return GPR_PREPOSITION;
];

This is the whole example, reworked as above:

Testing Color Token
"Testing_Color_token" by PB

[NB: can be made to compile in version 6M62, but crashes interpreter with an out-of-range memory access error, because relations as properties are not implemented correctly in 6M62; works as written here in 10.1.0 (in Borogove app); works as written in 10.1.2.]

[This is a more general solution than BadParser's, achieved by creating an integer variable to hold the I6 address of a topic calling Inform's [color] token, and using the former to parse a [color] token within a custom Understand token for parsing a second color value in a command.]

Lab is a room. 

Section 1 - Zed Lopez's Bubbling Beaker Award Winner #5

color is a kind of value.

the colors are brown, red, orange, yellow, green, blue, indigo, violet.

a color has a relation of a color to a color called the mixmaster.

to (c1 - color) mixed with (c2 - color) is (c3 - color):
  now the mixmaster of c1 relates c2 to c3;
  now the mixmaster of c2 relates c1 to c3;

to decide what color is (c1 - color) mixed with (c2 - color):
  if c2 relates to a color by the mixmaster of c1, decide on the color that c2 relates to by the mixmaster of c1;

when play begins (this is the set up colors still on the TODO list rule):
	red mixed with blue is violet;
	red mixed with yellow is orange;
	yellow mixed with blue is green;
	

Section 2 - Bad Parser's Bubbling Beaker Award Winner #6 Modified for Use with a cast topic variable

default-color-token is a number that varies.

First when play begins: set color token.

To set color token:
	now default-color-token is the topic "[color]" as a number.

Include (-
Global second_color;
[ second_color_token x firstnum;
	firstnum = parsed_number; ! saves the first parsed number locally
	x =  (+ default-color-token +)(wn); ! <- I7 representation of address of topic calling [color] token GPR routine, called with wn as parameter
	print "{default_colour_token returned ", x, "}^";
	if (x == GPR_FAIL or GPR_REPARSE) return x;
	second_color = parsed_number; ! saves the second parsed number into a global
	parsed_number = firstnum; ! returns the first number to its usual global spot
	return GPR_PREPOSITION;
];
-).

The second color understood is a color that varies.
The second color understood variable translates into I6 as "second_color". [represented as a number in i6]

The Understand token second-color translates into I6 as "second_color_token".


Section 3 - Mixing It Up

Mixing it with is an action applying to one color.

Understand "mix [color] with [second-color]" as mixing it with.

Report mixing it with:
	say "Mixing [color understood] with [second color understood] (in this implementation at least) yields: [color understood mixed with second color understood].".
	
test me with "mix red with blue / mix blue with red / mix blue with yellow / mix violet with yellow / mix blue with blue / mix blue with vermilion".

Section - Casting

To decide which K is (v - a value) as a/an/-- (name of kind of value K): (- {v} -).
To decide which topic is the topic (T - a topic): decide on T.
4 Likes

Bravo, Doc!

This week’s prize, Bubbling Beaker Award® #20 is presented to @borg323. It’s a little outside the typical bailiwick for these awards, because it involves hacking interpreters instead of Inform – in this case to allow the full range of Unicode characters (including for input) on the Z-machine.

It’s hard to use the Z-machine for much with current versions of Inform, but there was less overhead in previous versions, for which occasional questions still pop up. Also, it’s work like this on the “hardware” side that sets the stage for later fun on the programming and playing sides, and I salute any effort to keep the Z-machine useful.

borg323, if you would like to do a little show-and-tell about your demonstration, I’m sure that many people would be interested to learn more about the kind of challenges that you needed to overcome to get it to work.

4 Likes

Thanks, I really appreciate it.

There is not much to say about the hack, so I’ll give a little backstory first. A few years back I was looking for a z-machine interpreter with a license compatible with GPL but not itself GPL licensed. The best one I could find was jzip, but didn’t meet some of my other criteria (namely work on windows console and run zchess) so I decided to update it. This resulted in my jzip version and an unfinished and unreleased version with extensive Unicode support, which in turn allowed me to help improve frotz’s Unicode support a bit.

How this led to the hack in question: As part of the above work in jzip and frotz, I realized both use UCS-2 (that is 16 bit) encoded characters internally, and the move from that to UTF-16 encoded characters is small. In brief, UTF-16 is a variable length encoding that also uses paired symbols in a reserved subset of the 16 bit range (called a surrogate pair) to encode characters outside the 16 bit range. So just defining the required two parts of the surrogate pair as two individual Unicode characters and printing them in he correct order was enough to make it work on windows frotz, and a very small change in the jzip output code to make it work in the linux terminal.

Here is the I6 source (cut from the slightly longer example in my original post):

Switches xv5;
Release 1;

Zcharacter table + '@{d83d}' '@{de03}' '@{de23}' '@{de08}' '@{de3f}' '@{de07}' '@{de34}';

[ main;

  print "^UTF-16 surrogate pair test v0.1, by Borg^";
  print "Based on Unicode Test v1.0, by David Kinder^";
  new_line;
  print "Testing UTF-16 surrogate pairs.^";
  print "This sentence should end with 6 emoticons: @{d83d}@{de03} @{d83d}@{de23} ";
  print "@{d83d}@{de08} @{d83d}@{de3f} @{d83d}@{de07} @{d83d}@{de34} ^";
  new_line;
];
3 Likes

This week’s prize, Bubbling Beaker Award® #21 is presented to @Draconis. Once again, the award is presented for the extension that is included in the post, rather than the post itself. This time, Draconis demonstrated that it was possible to preserve formatting while capturing text for later use – something that can’t be done using Inform’s built-in routines. In addition to enabling capture of text printed in [bold type], [italic type] or [fixed letter spacing], it allows the user to capture other kinds of formatting (such as Glulx text styles) with a little work.

This makes Draconis’s fourth BBA – congratulations! I unfortunately understand only the broadest outline of how this is being accomplished, so hopefully Draconis will provide a little more detail here.

(Note for anyone wanting to try this out: The extension is in alpha or beta stage, and there may be some bugs. There is also an updated version of the extension available at the end of that thread.)

5 Likes

I am once again honored by this beaker of strange, suspiciously bubbling fluid!

The problem I was trying to solve in this case is that text substitutions like [italic type] aren’t actually part of the stream of text, as far as the game is concerned. Instead, they’re instructions to change the screen’s output parameters at a certain point in the printing process. If you print the text to anything that’s not the screen, like the transcript, or a buffer in memory (how Text Capture works)…the parameter-changing instructions execute, and just have no visible effect, since the screen isn’t displaying anything at the moment. The text left in the transcript or buffer shows no trace of them.

But, does it have to be that way? [italic type] isn’t any special case built into the compiler—it’s defined in standard Inform code. What if we changed that definition?

Specifically, what if we changed that definition so that, if Text Capture is currently active, it prints “␛i” instead, where ␛ is some special character that will never get used outside of our new extension?

Of course, when the Text Capture buffer is printed back out, it’ll display “␛i” instead of turning the text italic. But we know that this “␛i” code will only ever exist in this one special buffer! So we simply create a new say-phrase to print this buffer, paying special attention to these codes.

Specifically, it runs through the buffer one character at a time. If it sees ␛, it takes the next character and passes it to an I7 rulebook. Otherwise, it prints the character to the screen. That rulebook just watches for “i”, “b”, “r”, “f”, and “v” (italic, bold, roman, fixed, variable) and sets the appropriate style. Why a rulebook? Well, authors generally won’t have to define their own rules for this, but extension authors might! This makes it easier for them to avoid conflicting with each other. Someone doing a Glulx text styling extension might want to add a suite of codes for all the special semantic styles, while another person might want to deal with text colors. Now those two extensions don’t have to modify the same block of I6 (which always causes problems). They just have to choose different codes.

(The codes being a single character is kind of limiting, for both of those use cases. That’s part of why I call it still a beta. But it works fine for the five styles provided by the Standard Rules, and that’s what I needed it for—to avoid losing formatting with Implicit Actions, which relies on Text Capture. Why does Implicit Actions need Text Capture? Because it only wants to print the results of an implicit action that fails, so it runs the action, captures all its output, and then decides if it should print it or not.)

Really, the most difficult part of this was figuring out why Inform wouldn’t let me print my ␛ character (I wanted to use the ASCII control code for “escape”). It turns out Inform has built-in guardrails to avoid printing certain characters to the screen, and those guardrails also keep them from being printed to memory buffers. So I had to hack around those.

7 Likes

This week’s prize, Bubbling Beaker Award® #22 is presented to @Zed. Zed’s post is a rarity in mad science – effective use of the Preform grammar to introduce a new control phrase that is a significant convenience, patterned on the Python for loop.

For example, it is very easy to step through the values in a list:

Small primes is initially {1, 3, 5, 7, 11, 13, 19}.

When play begins:
	for each N in small primes:
		say "[N] is prime."

or instances of a kind matching a description of values of that kind:

When play begins:
	for each P in female people:
		say "[P] is a lady."

This is the same functionality that is currently provided via repeat with <variable> running through..., so it’s not exactly something new under the sun. Use of Preform is almost nonexistent due to its complexity and relatively incomplete documentation, but it allows things that would not otherwise be possible, so this merits an award for making some of Inform’s deepest features a bit more accessible.

This makes Zed’s fourth BBA, putting him neck-and-neck with the other two at the top of the leaderboard. Congratulations, Zed! I do hope that you will be willing to do some explaining about this one, which combines several advanced ideas into one beautiful bit of functionality. (Also, I suspect that many authors will want to make use of it, and it seems well-suited to take the form of a short and sweet extension.)

7 Likes

Some two and a half years ago, when I was still but a slightly deranged scientist, I wrote Strange Loopiness, which provided do-until loops (i.e., the body was always executed at least once; the test to continue happens at the bottom, even though in the code the condition appeared at the top), loops that descended for arithmetic values or enumerated kinds of value, a loop for simply looping N times, and, for each kind of loop, a variant that could maintain a separate index variable (or a table row number for the table cases). It was one of the components of the Code extension.

I’ve spent a bunch of time examining Ron Newcomb’s Original Parser extension, a re-implementation of I7’s parser in I7. It makes extensive use of custom control phrases, as in Original Parser: Volume Translation, / Book Inform 7 Mini-extensions / Chapter 9 - Other Loops :

To repeat forever begin -- end: (- while (true) -).
To while this isn't done begin -- end:  (-  do  -).
To do this/that again unless/until (c - a condition): (- until ({c}); -).
To do this/that again if/while (c - a condition): (- until (~~({c})); -).
To that/this is done if/while/when (c - a condition): (- until (~~({c})); -).
To that/this isn't done unless/until (c - a condition): (- until ({c}); -).
To (ph - a phrase) until (c - a condition): (- do {ph} } until ({C}); !-).

or Original Parser: Volume Translation, / Book - Array Phrases / Chapter - Get and Set a particular element for arrays of word values / Section 2 - Searches, Loops, and Ifs :

To repeat through (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number) begin -- end:
(-	{-require-ctvs}ct_0 = {arr};
	for (ct_1=0 : ct_1<{size} : ++ct_1)  -).

To repeat through (arr - a 0-based index based rulebook producing a value of kind K) of size (size - a number) starting at (min - a number) begin -- end:
(-	{-require-ctvs}ct_0 = {arr};
	for (ct_1={min} : ct_1<{size} : ++ct_1)  -).

To repeat through (arr - a 1-based index based rulebook producing a value of kind K) begin -- end:
(-	{-require-ctvs}ct_0 = {arr}; cacheval1 = ct_0-->0;
	for (ct_1=1 : ct_1 <= cacheval1 : ++ct_1)  -).

To repeat through (arr - a 1-based index based rulebook producing a value of kind K) starting at (min - a number) begin -- end:
(-	{-require-ctvs}ct_0 = {arr}; cacheval1 = ct_0-->0;
	for (ct_1={min} : ct_1 <= cacheval1 : ++ct_1)  -).

To repeat through all (struct size - a number) columns of (arr - a 1-based index based rulebook producing structs) begin -- end:
(-	{-require-ctvs}ct_0 = {arr}; cacheval1 = ct_0-->0;
	for (ct_1=1 : ct_1 <= cacheval1 : ct_1 = ct_1 + {struct size} )  -).

There are lots of custom to if phrases, as well (not To decide if, but To if). It impressed on me how useful custom control phrases could be, as well as teaching me a lot about how to make them (though Original Parser was for 6G60 and there have been many changes to details of low-level interfacing with I6 since then). When I write things involving a new kind of value now, I frequently write a custom to repeat phrase when it’s useful.

I found it frustrating, though, that I had to start all my loops with to repeat; I couldn’t make loop phrases that began with a different word. But repeat for item in obj-list and repeat until x is true aren’t so bad.

I’ve poked around in Syntax.preform (link is to 9.3/6M62’s) a little, trying to figure out some details of I7 syntax. I realized there was a massive amount of potential there for customizing Inform, but I had never gotten to tackling how.

In the thread you linked, @Natrium729 and @Draconis gave me the essential basic clues for how to make a new loop keyword. With those in hand, writing the to for phrases was trivial: they’re just variants on existing to repeat phrases copy-pasted from Basic Inform (or, prior to 10.1, the Standard Rules). But I’ve found Inform’s repeat phrases to be overly verbose and, personally, I think for each N in small primes reads better than repeat with N running through small primes.

Yes, an updated Strange Loopiness is definitely planned.

4 Likes

I have to warn that it might not work in a regular extension. Preform inclusions only work if they are read early enough (which is not surprising since they define Inform’s syntax), and that happens only if they are included in the main source file, or in language extensions (defined by language bundles). And even in the latter case it doesn’t work for all the Preform production. (As usual, I’m talking about 6L38.)

Like, I have that bit of Preform Zed used pasted in all my projects’ main source file so that I can write my control phrases in French.

That’s supposed change with the next version of Inform where Preform syntax can be bundled with extensions, though.

1 Like

I’m not going to try to do it for 10.1. I think it should be doable in the current development version.

This week’s prize, Bubbling Beaker Award® #23 is presented to @Celtic_Minstrel. The award-winning post is yet another one concerning emulation of ternary relations, this time in an effort to couple descriptions to both directions and rooms so that player commands like >LOOK WEST can be handled with some degree of elegance. Of particular note is the compactness of the structure of assertions, which make use of the underutilized with keyword to establish properties for nameless instances of a viewpoint kind.

The result is very easy to read and apprehend with no extraneous clutter. Although (as noted by the author) the choice of names for the implemented relation and associated verb may not be to one’s liking, these are easy enough to change. The associated machinery for delivering the viewpoint description can also be streamlined. However, the presented solution is one of those rare instances of “proper” Informese that begins (as @Eleas once put it) to resemble a description of the problem to be solved.

Congratulations to Celtic_Minstrel for this first BBA award! (My guess is that it won’t be the last.) As always, the winner is invited to say a few words about the winning post as described above in “The Rules.”

Honorable mention goes to @Zed for the immediately preceding post, which proposes an Assert rulebook to make run-time assertions at the start of play more palatable.

5 Likes