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

PS, I want to claim mad scientist title for this abomination :stuck_out_tongue:

6 Likes

That’s horrible. I’m in awe.

4 Likes

This week’s prize, Bubbling Beaker Award® #10 is presented to @matt_weiner, who was at one point a very frequent contributor to the forum but has not been by for several years. (Honorable mention is given to @Skinny_Mike, who provided significant technical assistance at how and when does I7 guess which indirect article to use? - #19 by Skinny_Mike) Matt’s award-winning post concerns a method of improving Inform’s handling of irregular indefinite article selection in the context of conditional printed name output. The running example is a unicorn, which may or may not be honest – out-of-the box, I7 will prefer “an unicorn” and “a honest unicorn” when printing the name of this animal.

The code wasn’t quite perfected in the original post, but I’ve taken the liberty of updating and streamlining it a bit. Details will be given in a follow-up post.

3 Likes

Why is this interesting? Because I7’s default method of choosing whether to use “a” or “an” as an indefinite article is quite prone to error when faced with words that have irregular pronunciation. In short, it always picks “a” when the word starts with a consonant and “an” when the word starts with a vowel. (Note that the only vowels recognized by I7 are a, e, i, o and u, so y always counts as a consonant.)

Although the template code is good about ensuring that the entire printed name is considered (including anything prefixed via a Before printing the name... rule), as well as the number word for a plural group of identical objects such as “six hourglasses,” it has no provision for deeper phonetic analysis. English is just about the worst language for attempting to predict pronunciation from spelling, so the reason for this gap is easy to appreciate.

The solution presented is not particularly elegant; all it does is create exception tables that are checked when deciding which indefinite article to use. Any word that the author wants to act differently than the default is simply added to a table. Performance is not great, but it’s not a major problem for the limited number of exceptions that any one story is likely to require.

The following example code is for 6M62, but conversion to 10.1 is straightforward. Note that it is written for Glulx only:

An Honest Unicorn
"An Honest Unicorn"

The Lab is a room.

An animal can be honest or dishonest. Understand the honest property as describing an animal.

Before printing the name of an animal (called beast):
	say "[if beast is dishonest]dis[end if]honest ".

The unicorn is an honest animal.
The yak is a dishonest animal.

A block is a kind of thing. It has a number called weight. The description of a block is “It looks to be about [weight of the item described in words] pound[s].”

After examining a block (called B):
	now the indefinite article of B is "";
	now the printed name of B is “[weight of B in words]-pound block of [B]”.

The yttrium is a block with weight one. The indefinite article of the yttrium is "some".

The uniform is wearable. The description is “It’s a United States Army uniform.”

After examining the uniform for the first time:
	now the printed name of the uniform is “United States Army uniform”.

A unicorn, a uniform, an hourglass, a yak and yttrium are in the lab. An Æsop anthology is in the lab.
A rock, a pebble, and an umbrella are in the lab.

Table of Words That Start With Vowel Sounds
word
“Æsop”
“hour”
“hourglass”
“honest”
“yttrium”

Table of Words That Don’t Start With Vowel Sounds
word
“one”
“uniform”
“unicorn”
“united”
“United”

To decide whether (string - a text) starts with a vowel sound (this is vowel sound checking):
	let the first word be punctuated word number 1 in string;
	if the first word is a word listed in the Table of Words That Start With Vowel Sounds, yes;
	if the first word is a word listed in the Table of Words That Don’t Start With Vowel Sounds, no;
	if character number 1 in the first word is a vowel, yes;
	no.

To decide whether (letter - a text) is a vowel:
	let lower be letter in lower case;
	if lower is:
		-- "a": yes;
		-- "e": yes;
		-- "i": yes;
		-- "o": yes;
		-- "u": yes;
		-- otherwise: no.

The initial sound rules are a rulebook. The initial sound rules have outcomes vowel and consonant.

An initial sound rule (this is the basic initial sound test rule):
	let temp be the substituted form of “[I6 buffer]”;
	if temp starts with a vowel sound:
		vowel;
	otherwise:
		consonant.

To say the/-- I6 buffer:
	(- PrintI6Buffer(); -).

Include (-

[ PrintI6Buffer len i;
	len = StorageForShortName-->0;
	for ( i = WORDSIZE : i < len + WORDSIZE : i++ ) {
		glk_put_char(StorageForShortName->i);
	}
];

-).

[The only change of interest in this inclusion is to routine PrefaceByArticle]
Include (-

Global short_name_case;

[ PrefaceByArticle obj acode pluralise capitalise i artform findout artval;
	if (obj provides articles) {
		artval=(obj.&articles)–>(acode+short_name_case*LanguageCases);
		if (capitalise)
			print (Cap) artval, " ";
		else
			print (string) artval, " ";
		if (pluralise) return;
		print (PSN__) obj; return;
	}

	i = GetGNAOfObject(obj);
	if (pluralise) {
		if (i < 3 || (i >= 6 && i < 9)) i = i + 3;
	}
	i = LanguageGNAsToArticles-->i;

	artform = LanguageArticles + 3*WORDSIZE*LanguageContractionForms*(short_name_case + i*LanguageCases);

	#Iftrue (LanguageContractionForms == 2);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms == 3);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms == 4);
	if (artform-->acode ~= artform-->(acode+3)) findout = true;
	if (artform-->(acode+3) ~= artform-->(acode+6)) findout = true;
	if (artform-->(acode+6) ~= artform-->(acode+9)) findout = true;
	#Endif; ! LanguageContractionForms

	#Iftrue (LanguageContractionForms > 4);
	findout = true;
	#Endif; ! LanguageContractionForms

	#Ifdef TARGET_ZCODE;
	if (standard_interpreter ~= 0 && findout) {
		StorageForShortName-->0 = 160;
		@output_stream 3 StorageForShortName;
	if (pluralise) print (number) pluralise; else print (PSN__) obj;
	@output_stream -3;
	}
	#Ifnot; ! TARGET_GLULX
	if (findout) {
		if (pluralise)
			VM_PrintToBuffer(StorageForShortName, 160, EnglishNumber, pluralise);	! MODIFIED
		else
			VM_PrintToBuffer(StorageForShortName, 160, PSN__, obj);	! MODIFIED
	}
	#Endif; ! TARGET_

	acode = acode + 3*LanguageContraction(StorageForShortName + WORDSIZE);	! MOVED
	Cap (artform-->acode, ~~capitalise); ! print article
	if (pluralise) return;
	print (PSN__) obj;
];

-) instead of “Object Names II” in “Printing.i6t”.

[The only change of interest in this inclusion is to routine LanguageContraction]
Include (-

Constant LanguageAnimateGender = male;
Constant LanguageInanimateGender = neuter;
Constant LanguageContractionForms = 2; ! English has two:
! 0 = starting with a consonant
! 1 = starting with a vowel
[ LanguageContraction text    result rv i;
	!!! This is the old routine:
	!if (text->0 == ’a’ or ’e’ or ’i’ or ’o’ or ’u’
	!or ’A’ or ’E’ or ’I’ or ’O’ or ’U’) return 1;
	!return 0;
	!!! Out w/ old – in w/ new:
	rv = FollowRulebook( (+ initial sound rules +), nothing, true );	! suppress line breaks
	if ((rv) && RulebookSucceeded()) {
		result = ResultOfRule();
		if (result == (+ vowel outcome +)) return 1;
		return 0;
	}
	return 0;
];

Array LanguageArticles -->
! Contraction form 0: Contraction form 1:
! Cdef Def Indef Cdef Def Indef
"The " "the " "a " "The " "the " "an " ! Articles 0
"The " "the " "some " "The " "the " "some "; ! Articles 1

! a i
! s p s p
! mf n m f n m f n m f n
Array LanguageGNAsToArticles --> 0 0 0 1 1 1 0 0 0 1 1 1;

-) instead of “Articles” in “Language.i6t”.

The code is oriented around special cases, so to make use of it you’ll want to update the two tables that handle these. The Table of Words That Start With Vowel Sounds is for words like “honest” that begin with a written consonant but spoken vowel. The Table of Words That Don’t Start With Vowel Sounds is for words like “unicorn” that begin with a written vowel but a spoken consonant.

As seen in the example code, special characters depicting ligated vowels can also be handled.

3 Likes

What happens when an American and an Englishman converse about “herbs” in general in the game?

4 Likes

This week’s prize, Bubbling Beaker Award® #11 is presented to @Draconis, which marks his second win. His award-winning post lays out a method for allowing the player to issue commands to multiple NPCs in a group, a la Infocom’s Suspended. I haven’t delved into the technical details, but from what I understand about the parser, this is not a simple modification. This work was developed into a public extension.

Congratulations, Draconis! Perhaps you would be willing to say a few words about what it took to get this to work?

[An important note: Award frequency will be decreasing to every other week between now and the end of the year, so the next award will be for Fri Nov 17.]

5 Likes

Oh, man, this one was a while ago! I need to refresh my memory for how this works!

Okay. Let’s imagine we’re giving the command ALICE AND BOB, GO NORTH.

The way the parser works by default, if it sees a comma, it jumps back to the beginning of the command and calls NounDomain. NounDomain is the routine that tries to parse the name of a single object, so most of the other parsing routines delegate to it.

That’s the bit that I modified: instead of calling NounDomain, I make it instead call ParseToken(ELEMENTARY_TT, MULTI_TOKEN)—that is, try to parse a “[things]” token.

This “[things]” token will fail, because we already know the list of actors ends with a comma—that’s how we ended up here in the first place. (If “[things]” sees something after a comma that it can’t understand, it thinks there’s a bad entry in the list.) But as it works, it puts the list of things it parses into the multiple object list, and it doesn’t clear that out when it fails! So now the multiple object list is {Alice, Bob}.

Once it fails, we go back into Inform 7 by calling the “multiple actor rulebook”, which takes the multiple object list, makes sure it’s a valid list of people, and stores it in a global variable of its own. It then takes the rest of the command—everything that comes after—and sticks this in another global variable. If this rulebook succeeds (the list was valid), then the parser continues after the comma, attempting to parse the rest of the line as a normal command. If it fails, then the parser reacts the same way it originally would if NounDomain failed—deciding the part before the comma isn’t an actor name after all, and trying to parse it as a verb instead (the .NotConversation label).

So now we have the list of intended actors {Alice, Bob} stored in a global variable, and the intended command GO NORTH in another global variable. Once we have this, a “rule for reading a command” takes over. As long as the list of intended actors isn’t empty, we set the player to the first element of that list, and parse the intended command. The player is now Alice, and the parser is given the text GO NORTH.

But wait, we don’t want this action to be performed as the player! We want it to be performed by an NPC! So a “first before doing anything” rule sees what’s happening, resets who the player is, and turns the action into a request: “going north” becomes “asking Alice to try going north”. It executes that action, then removes the first entry from the intended actor list.

This continues until the multiple actor list is empty. Since the command is actually parsed separately for each actor, it can deal with the actors having different surroundings: ALICE AND BOB, TAKE ROCK when each one has a different rock in their room, for example. The “rule for reading a command” also pauses when a disambiguation question is asked, letting the player answer before going back to multicommand processing.

As a side bonus, the extension also creates the action COMMAND [things] TO [text], which I used to test the multicommand machinery. In other words, this extension gives you a way to create new actions that convert to giving orders, which normally is nigh impossible! It just puts the list of actors and the intended command into the appropriate global variables, without any of the parser trickery.

9 Likes

Side note: the way Suspended does it is actually hilariously simple by comparison! It goes like this:

  • When parsing an actor, look for the exact phrase BOTH [person] AND [person]. If so, set the actor to a special “both” value.
  • If the “both” actor is commanded to perform any action except moving Fred, give an error.
  • Make sure all the actors involved are in the location of Fred.

Notably, it never even bothers to check that there are two different people involved! You never need multiple robots to accomplish something, even in the one specific place in the game where this syntax works—you can always say BOTH SENSA AND SENSA, MOVE FRED!

6 Likes

This week’s prize, Bubbling Beaker Award® #12 is presented to @capmikee, who used to be a frequent contributor to the forum. His award-winning post is really just a side note regarding a conversation extension that he was working on, but it demonstrates a method for handling a situation in which the author would like the grammar line for a two-object action to handle only the second noun.

Congratulations to capmikee! Because he has been inactive for so long, I will provide a short write-up in a follow-up post.

[An important note: Award frequency will be continued at every other week between now and the end of the year, so the next award will be for Fri Dec 01.]

6 Likes

This week’s award is interesting because the compiler will disallow code such as:

Quizzing it about is an action applying to one thing and one visible thing.

Understand "ask about [any known thing]" as quizzing it about (with nouns reversed).

The produced Problem message states: “...you can't use a 'reversed' action when you supply fewer than two values for it to apply to, since reversal is the process of exchanging them.” Despite use of the keyword “supplying,” the compiler does not recognize attempts to provide a rule for supplying a missing noun... as a valid way to address its concerns.

In capmikee’s post, he shows that a for supplying a missing second noun... rule can be set up to manipulate the noun and second noun manually:

For supplying a missing second noun when an actor quizzing something about:
	now the second noun is the noun;
	now the noun is the interlocutor of the person asked.

(Note that the interlocutor is a property of people in the context of his sample code, and the property is updated by rules for speech actions.)

The logic for assignment of the noun must be provided by the author in this case, as there is logic in the parser that will demand a non-nothing noun at run-time. (This is because the action definition in this case specifies that it applies to one thing and one visible thing.) However, the redefinition of the nouns persists throughout the remainder of action processing, so rules in other rulebooks can be constructed with action patterns that conform to the way that the author thinks about the action. For example:

After quizzing Bob about the soccer ball:
	say "Bob says, 'There's only one kind of football!'"

A question about does the player mean... (DTPM) rules comes up in capmikee’s post, but it is not addressed in detail. As he notes, the DTPM rules are handled prior to rules for supplying missing nouns, so there is a slight hitch in writing naturally-structured DTPM rules. For example, the rule

Does the player mean quizzing Bob about the soccer ball:
	it is likely.

will not work as expected because, even though the command entered might be >ASK ABOUT BALL, at the time that the DTPM rule is processed the noun is soccer ball and the second noun is nothing.

Fortunately, this is not hard to work around by also performing the switch within the DTPM rules. There’s a bit of a technicality here regarding the noun and second noun while processing DTPM rules, in that the assignments are temporary and will be undone at the end of processing the rulebook. Consequently, the manual swapping must be done twice: once during DTPM and once during supplying missing nouns.

Here’s a quick sample block showing the essentials in action:

DTPM with Noun Changes
"DTPM with Noun Changes"

Place is a room.

Bob is a man in Place.

A thing can be known.

A tennis ball is a known thing. A soccer ball is a known thing.

Quizzing it about is an action applying to one thing and one visible thing.

A thing has a thing called the interlocutor. [things not people, to leave room for "talkable" objects]

The interlocutor of the player is Bob. [set manually for brevity of example]

Understand "ask about [any known thing]" as quizzing it about.

To decide which object is the current actor: [needed because swaperoo rule is not in an action-processing rulebook]
	(- actor -).

This is the swaperoo rule:
	now the second noun is the noun;
	now the noun is the interlocutor of the current actor.

First does the player mean quizzing about: [placed first so that other DTPM rules see post-swap nouns]
	follow the swaperoo rule.

Does the player mean quizzing about the soccer ball: [soccer ball is *second* noun]
	it is likely.

For supplying a missing second noun when an actor quizzing about:
	follow the swaperoo rule.

Carry out quizzing about: [to show that the noun and second noun assignments persist from supply missing nouns stage]
	say "quizzing [noun] re: [second noun]..."

After quizzing Bob about the soccer ball: [sample specialized response]
	say "Bob says, 'There's only one kind of football!'"

In practice, a general report ... quizzing ... about ... rule to handle non-special cases would also be desirable, but that’s left as an exercise to the reader.

7 Likes

This week’s prize, Bubbling Beaker Award® #13 is presented to inimitable @Zed. It demonstrates a minor modification to the parser’s built-in ScoreMatchL() routine to ensure that it understands possessive pronouns when they apply to parts of a person instead of just things being carried by that person. (The OP is using the example of a command like >X HIS EYES.)

While this award-winning post may lack the scale of some of the more ambitious recipients, there is no doubt that it provides a general improvement in parsing. In addition to its utility, this post was selected because it’s a good example of the kind of small tinkering that starts one on the road to mad scientisthood, and so may encourage others to begin the journey.

Congratulations, Zed, on this your third BBA! I hope that you’ll drop by to say a few words about it; if not, then I’ll post a brief overview before the next award.

[An important note: Award frequency will continue at every other week between now and the end of the year, so the next award will be for Fri Dec 15.]

11 Likes

@Zed makes the following observation on the parser deficiency leading to this fix:

This is an oversight of the parser’s. When it’s evaluating the applicability of something with a possessive pronoun, it discounts parts and only looks at what the person has (i.e., carries or wears).

It would be more accurate to say that this is one of a number of places where the Inform parser has not been updated to take account of developments elsewhere in the language (in this case the introduction of ‘parts/incorporation’). At the time it was written, the existing code was fine.

5 Likes

aw, shucks! I didn’t even slightly remember this. :grinning: There’s not a lot to say about it: it was a fairly trivial tweak, with the only trick being finding where to make the trivial tweak.

Graham accepted this patch (PR #47), so it’ll be in the next version of Inform.

5 Likes

A quick overview of the details of Zed’s award for the beginner mad scientist…

The ScoreMatchL() routine (stands for “score match list”) is a routine inherited with some modifications from the I6 Standard Library. Its job is to generate a score that is used for automatic disambiguation of nouns, and it checks a number of conditions to decide on a composite score. If a player command is ambiguous about the noun (say >TAKE COIN when both a silver coin and golden coin are available) but ScoreMatchL() is able to determine a single highest scoring item, then it uses this “best guess” as the automatic choice, resulting in output like:

>TAKE COIN
(the silver coin)

Taken.

The I7 features that interact with this functionality directly are the does the player mean rules and the clarifying the parser's choice... activity, but the parser also has some I6-level functionality about possessive pronouns with special logic in this routine. This logic insists that certain I6-level conditions (which can’t be modified at the I7 level) are true in order to consider an object as a valid choice for the noun at all – if the conditions aren’t met, then the object is disqualified.

As drpeterbatesuk points out, the condition checked for a command like >X HER EYES is that the object matching the word “eyes” is an object tree child of the associated pronoun (in this example, “her”). In I6, if one wanted a person to have separate eyes as part of their body, the eyes object would be a child of the woman object in the object tree. In I7, the incorporation relation is handled with a separate, distributed object tree structure, which is missed by the older I6 logic, so even though the eyes object would match the word “eyes” in the player command, ScoreMatchL() would disqualify the object because it doesn’t understand the incorporation relation and can’t relate it to the woman object.

Zed’s change updates the logic to account for incorporation, so problem solved.

5 Likes

This week’s prize, Bubbling Beaker Award® #14 is presented to that paragon of productive mad science, @drpeterbatesuk. His award-winning post is ironically on this very thread! It shows a method for fetching the address of compiler-generated parsing routines for properties by crafting a special-purpose grammar line. This is useful when one wants to be able to have an action that uses two kinds of value (in this case, a colour and a colour), which is normally disallowed by the I7 compiler. (It’s also useful as an example of delving into the dictionary and grammar line structures generated at the I6 level, an obscure bit of knowledge.)

EDIT: I just realized that the way that I described the good doctor’s post above, it looks like a repeat of Award #6, given to BadParser. It’s true that the code takes the form of a solution to the same problem, but what’s significant and award-worthy is the way that it shows how to extract the relevant routine address from the compiled grammar lines, so that the code works even if the location of the routine changes. If one is trying to use a grammar line with two kinds of value, then the particular improvement here is that it can be set up once and then forgotten about, instead of having to hand-tweak the address with every new compilation.

Congratulations, drpeterbatesuk, on your own third Bubbling Beaker®! (Perhaps I should start a leaderboard.) Since your explanations are always excellent examples of completeness and clarity, there may not be much to add over what’s already been posted, but please feel free to take the floor.

[An important note: Award frequency will be continuing at every other week between now and the end of the year, so the next award will be for Fri Dec 30.]

8 Likes

I don’t think this is quite correct. That arrangement would imply that the eyes are either carried or worn by their owner, which often will not be what’s really wanted. The trick used in I6 that functionally most closely approximates the uses of I7’s incorporation relation is the property add_to_scope through which an author can add a list of objects that will always be in scope when the providing object is in scope. For this example, that means that whenever the player can interact with a given person through typed commands, they can also interact with that person’s eyes- even though the eyes are not children in the object tree of that person or even necessarily nearby (usually they will be kept offstage).

EDIT: this subterfuge works quite well for some purposes, but not others- e.g. a test for whether the person’s eyes are indirectly in the same room as their owner will fail.

3 Likes

It’s been a while since I’ve done any pure I6, but the approach that I recall using in the past is to mark the person object as transparent (so that children are placed in scope) and have any component parts be direct children.

Under that scheme, I guess body parts are indistinguishable from objects carried by the person object in terms of object tree and attribute state, but use of a BodyPart class with an appropriate before property (to handle ##Take and the like) always seemed like a good enough way to handle the functional distinction, if needed. (The default handling is Those seem to belong to the woman. Accurate, if understated.) I never really liked to use add_to_scope because of the “not really there” aspect to which you’re referring.

You made me curious, so I wrote a test scenario. Under StdLib 6/11, built-in possessive pronoun handling doesn’t work with objects handled via add_to_scope, for much the same reason that they don’t work in I7. In the scenario producing the following exchange, the eyes objects are handled via placement as direct child, and the nose objects are handled via add_to_scope:

Starting Point
An uninteresting room.

You can see a woman and a rock here.

>X WOMAN
Like you, she has a nose and eyes.

>X EYES
(your eyes)
You see nothing special about your eyes.

>X HER EYES
You see nothing special about the woman's eyes.

>X NOSE
You see nothing special about your nose.

>X HER NOSE
I only understood you as far as wanting to examine the woman.

>X HER ROCK
You see nothing special about the rock.

(The rock is interesting as a comparison. Because only one object matches via name, the restriction imposed by the possessive pronoun is ignored.)

3 Likes

Yes, that’s certainly another way in which add_to_scope falls short and the other method you describe performs better out of the box. Both of these ideas are explored in the DM4 (e.g. p.236 and Exercises 102 & 103 p.496).

EDIT: the standard way to deal with the possessive pronoun issue would be to either add ‘her’ to the name property of the eyes, or, slightly more sophisticated, write a parse_name routine so that her eyes is recognised.

2 Likes

This week’s prize, Bubbling Beaker Award® #15 is presented to @eu1, aka emacsuser, who was once a frequent visitor of the forum but has not been seen for almost ten years. The award-winning post demonstrates a method for understanding things by properties of their properties, via construction of a special-purpose relation. In the example, adjective words such as "wooden" are associated to materials such as wood, and each material thing has a material, so that a command like >X WOODEN STAFF will be correctly understood.

This may not sound like much, but it’s a trickier problem than it sounds like, and the core solution is quite elegant. This is the first Bubbling Beaker® awarded to eu1, but I doubt it will be the last – any mad scientist will learn many valuable tricks by studying the code that he has shared with the world. (The Object Kinds extension is also noteworthy.)

[An important note: Since a bi-weekly frequency of new awards seems to be working out OK, it will continue for the foreseeable future. Happy New Year!]

6 Likes

@eu1 is Brady Garvin, author of the invaluable Scopability and Object Kinds extensions. (I’ll publish my 10.1 sequel to Object Kinds someday…)

I have learned an enormous amount from his posts, though he was long gone before I got here.

3 Likes