Bubbling Beaker Awards (Award #33, February 07 2025)

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