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

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

This week’s prize, Bubbling Beaker Award® #16 is presented to @Draconis, who somehow continues to avoid forcible induction into the Mad Scientists’ Club. His award-winning post unleashes a new extension that allows for distance calculations between rooms via Dijkstra’s algorithm. The rulebook-based solution at its heart makes it fairly easy for an author to specify edge weights that vary under varying conditions, if desired, and for simple cases – such as making diagonal directions count as longer than cardinal ones, as one might wish for a region of rooms representing a grid of locations – only a few lines are required. (Note that the extension does not replace Inform’s built-in route-fnding logic; instead it creates parallel logic for the author to use instead. It does, however, disable fast route-finding for the built-in logic.)

Draconis, congratulations on your third BBA! The linked thread doesn’t contain a lot of under-the-hood discussion, nor does the extension documentation go into details – do you feel like giving a brief overview of the default path-finding functionality as compared to your implementation?

(Honorable mention goes to @drpeterbatesuk, who first proposed a limited solution for the particular case of treating diagonal directions different from cardinal directions at https://intfiction.org/t/best-route-and-diagonal-shortcutting/51085/16.)

3 Likes

I am honored with yet another suspiciously bubbling beaker! (And a bit embarrassed to say that I actually have no idea what the Mad Scientists’ Club is.)

This time the innovation comes from something I needed rather than an interesting problem on the forums! As you can see from that thread, it was for Death on the Stormrider. I needed precise control over NPC movements in order to make the puzzles work. Qarrad isn’t bothered by heat, but Bashti is; he can’t go through overheated rooms, but is also the only one who can use the crawlspaces, except he should avoid them unless absolutely necessary…

And after several failed attempts with Inform’s standard route-finding, I started looking for alternatives on the forum. That led me to this thread. I recommend reading the first post there for some cool technical details, but the essence is that Inform’s slow route-finding doesn’t actually use a standard algorithm. It’s something custom-built and not-well-documented.

Specifically, I needed an algorithm that could handle a “weighted graph”: a map where the connections between rooms can have different lengths, so going from A to B (weight 5) and B to C (weight 5) can actually be better than going from A to C (weight 20). Inform’s fast route-finding is the Floyd-Warshall algorithm, which can do this, but also calculates every possible route on the map at once—which is bad for my purposes, since I need to redo the calculations every time a different NPC is moving!

Specifically, I needed a “single-source shortest path” (SSSP) algorithm: a way to calculate the best route to every room from a single origin. This can run a lot faster than an “all-pairs shortest path” (APSP) algorithm like Floyd-Warshall. The linked thread uses a classic one called “breadth-first search”…which assumes every connection between rooms has the same length. Curses!

But there’s a very famous algorithm for the SSSP problem on a weighted graph, one of the classic algorithms that every CS student has to learn: Dijkstra’s algorithm, named after the father of computer science himself! There’s only one problem: the way it’s normally implemented requires fancy data structures like priority queues and Fibonacci heaps. At bare minimum, it needs a dynamic list that you can easily add and remove things from. And while I7 can do this easily, I6 cannot.

But, the things that get put in this list are rooms! And rooms are something I6 is very good at working with! I repurposed the room_index property (normally used for fast route-finding) to form a singly-linked list of rooms, with each one’s room_index being a pointer to the next. This gave me the queue I needed, so the algorithm could work!

After several failed experiments, I made it so that every room stores three relevant properties: the calculated distance, the room before this one on the route (formally, its parent in the minimal spanning tree), and the direction to go from that room to get to this one. Since the distance is stored as a property, you can use it to make a scalar adjective in I7: if you say “a room is near if its Dijkstra distance is is at most 3”, then you can look up “the nearest room”—or “the nearest unexplored light-filled room”, or as specific as you like!

The last problem to solve is—if this is a weighted graph, how do you specify the edge weights? How do you say that the connection from A to B is ten meters long and the connection from B to C is fifteen?

For Stormrider I could have done it in pure I6, but every game would want to do it differently. So I made it store the four relevant properties (room gone from, room gone to, direction gone, and door gone through) in global variables and call out to an I7 rulebook, which authors could customize as they pleased. You can make cardinal directions cost 10 and diagonal directions cost 14, for example, if you want to simulate some nice Euclidean geometry, or make closed doors cost 1 and locked doors cost 2 to represent how many turns it’ll take to go through them.

In the end, I’m very happy with it. Every NPC in Stormrider uses this to decide where to go next, with their own set of edge cost rules (“edge cost when the actor is Qarrad” and so on). And the adventurers in Labyrinthine Library of Xleksixnrewix use it too: every turn, they move toward the nearest unexplored room, with diagonal directions costing 1.4 times as much as cardinal ones. Blocking them out of rooms with barriers in them was just another edge cost rule!

For the future, I should probably create a convenience phrase that’s less clunky than “rule succeeds with result N”, and perhaps avoid the conflict with fast route-finding. And it has one significant drawback over the default route-finder, which is that you need to explicitly recalculate routes from your chosen origin before doing anything with them. But overall, I think it’s a solid improvement over the default implementation (in everything except speed—calling the rulebook every time it considers an edge is a serious cost!) and recommend using it for anything involving complicated pathfinding!

7 Likes

As a bonus, here are some of the edge cost rules from Stormrider and Labyrinthine Library.

Stormrider:

Edge cost rule: [Default pathfinding for most characters: don't use locked doors or crawl hatches, closed doors take 2 turns (one to open one to move), everything else takes 1 turn]
	if the edge door is a locked door, rule succeeds with result -1;
	if the edge door is a crawl hatch, rule succeeds with result -1;
	if the edge door is a closed door, rule succeeds with result 2;
	if connecting Shimat's Cabin and the Miscellany, rule succeeds with result -1; [I don't know why NPCs are getting stuck in Shimat's cabin but this should prevent it hopefully]
	rule succeeds with result 1.

Edge cost rule when the person asked is the concept of sound propagation:
	rule succeeds with result 1. [Sound doesn't care about doors, obstacles, etc; it can travel through every part of the ship]

Edge cost rule when the person asked is Bashti: [Bashti uses more elaborate route-finding than most]
	let the total be 1;
	[…several special cases to keep him out of other people's areas…]
	if the edge source is the Cargo Hold and the edge destination is the Lower Deck and the open tripod is in the Lower Deck: [Staircase is jammed, can't use it]
		rule succeeds with result -1; [Impassible]
	if the edge destination is an overheated boiler-room, increase the total by 10; [Bashti does not like the heat]
	if the edge destination is a crawlspace and the edge source is not a crawlspace, increase the total by 5; [Penalize entering the region but don't penalize moving through it]
	if the edge door is a locked door, increase the total by 2; [Unlocking doors is an inconvenience but not a fatal one]
	rule succeeds with result (the total).

Note that the edge between the Cargo Hold and the Lower Deck is only blocked in one direction—he’s free to route-find through it in the other direction, since he can unblock the door from below!

Library:

Edge cost rule:
	if the edge destination is barricaded or the edge source is barricaded, rule succeeds with result -1; [Cannot pathfind through a barricaded room - either for movement or for line of sight]
	if the edge direction is vertical, rule succeeds with result 12; [Disincentivize going up and down a little bit so that adventurers tend to stay on the same level]
	if the edge direction is diagonal, rule succeeds with result 14; [Approximation of 10×sqrt(2)]
	rule succeeds with result 10.
4 Likes

Thanks. Yes, I have been away for awhile.

3 Likes

Welcome back!

1 Like

The festivals of Saturnalia & Christmastide may officially be spent, but continuing to channel something of their impish spirit and in accord with the iconoclastic chaos epitomised by Mad Science at its best, @Zed & I (@drpeterbatesuk) have for one week only appointed ourselves the Lords of Misrule, deposing the diffident originator of the Mad Scientists Club in order to award Bubbling Beaker Award® #17 to @otistdog himself for a recent post offering a neat solution to an old parsing riddle.

The Inform parser is both a fascinating beast and something of an archaeological curiosity- in retaining many vestiges of its origin in the earliest incarnations of Inform (when the latter was little more than an assembler for Z-code); in its deliberately close functional and aesthetic affinity to Infocom’s historic parser; in the ways in which it was subsequently tweaked and refined to deal with bug reports, user requests and the specific requirements of some early canonical works of Inform; in how it was subsequently adapted to allow compilation to the Glulx virtual machine and then finally to be grafted into the Inform 7 revolution.

Although intriguing, one consequence of this convoluted history is that the parser remains about as far from a literate program as it is possible to get. Its sparsely-documented workings are frequently opaque and prove resistant to desultory study. Also, by the nature of the parser’s primary mission to deal as gracefully and unobtrusively as possible with the many uncertainties and ambiguities thrown up by (to a degree) freeform linguistic input, it tries to discreetly adjudicate with poise a great many edge-cases. To quote the Inform documentation itself (albeit on a slightly different subject)

The code … is highly prone to behaving unexpectedly if changes are made, simply because of the huge number of circumstances [it has to deal with], so change nothing without very careful testing.

As a result, most authors shy away from the parser and are happy to leave it to its own devices, like a mighty but fantastic caged brute, to be fed only reservedly through prison bars.

Not so the valiant winner of this week’s Peer-Revued™ Bubbling Beaker Award!

No newcomer to grappling with the parser, and with the gauntlet thrown down to resolve a highly-contrived edge-case of scarcely less antiquity than Fermat’s Last Theorem, without disturbing the slumber of the beast he entered its lair, plucked out a couple of sparkling gems, and fashioned them into an instructive and elegant few lines of code with potential for wider authorial application.

I will say no more, but leave our distinguished winner now if he wishes to take the floor and offer further insights into his perilous journey and winning solution.

3 Likes

I was informed by drpeterbatesuk and Zed that they would be granting this award, and I am delighted to accept. I am doubly honored for the Peer Revue™!

For those who missed it, the post concerned parsing a command >LIGHT LIGHT LIGHT in which each word has a different meaning in English (verb, adjective and noun). The good doctor has already explained the way that my solution works on that thread, and I put up an outline of the basic parsing process for nouns there, as well, so I won’t repeat much of that here. Instead, I’ll try to describe how the idea came to me.

There were three major pieces of background knowledge that went into it:

  • I’d been looking at snippets and their related built-in phrases quite a bit in the weeks before, as a consequence of various experiments with the Subcommands extension by Daniel Stelzer. I would guess that this was the most important, since it predisposed me to focus on the details of how nouns are matched to specific strings of words. (Side note: The original mad scientists formally welcome Draconis to the club!)

  • I have spent a lot of time looking at the way parsing is done at the I6 level. This is one of the deeper mysteries of the system; I eventually got to the point where I consider the source code to be the best documentation of itself. The process of parsing is fairly complex, but there are key ideas that can be expressed simply, and one of those is that when parsing nouns the first and most important distinction from the parser’s standpoint is how many words of the player’s command can be matched to a given object. If one object in scope matches more words in a row than any other, then the parser is as certain as it can be about deciding which object the player means.

  • The nature of conditional Understand... lines is something that I’ve explored in depth due to some previous questions that have come up on the forum. The important thing about them is that the condition is evaluated before the parser attempts to match the text (or topic) provided – if the condition is not true, the text/topic can’t match.

The flash of insight leading to the solution was that 1) the core of the problem is really that the parser ignores the significance of the repetition of the word “light,” and 2) if there were a way to differentiate the second instance of the word “light” then the problem would be solvable. The next thought was that maybe Subcommands could help, quickly followed by the realization that a subcommand can only be used after parsing is complete. The third step was recognizing that I wanted something like a subcommand for the words parsed up to that point (to see whether “light” had already been matched as a noun word), and that it would be easy to construct a snippet spanning those words using internal parser variables. That snippet could be used as the basis of a decision attached to a conditional Understand... line, which would allow the first instance of “light” in the noun words to be differentiated from any subsequent instance.

Come to think of it, part of the nucleus of the idea probably derives from a very enlightening blog post by zarf concerning various tricks for getting mileage out of parse_name routines in I6. (If you look closely, you can see the basic pattern he outlines in the way that I7 produces parse_name routines today.)

So, my cautionary tale is probably best summed up by some amalgam of quotes involving the shoulders of giants, the relative contributions of inspiration vs. perspiration, and the danger of gazing too long into the abyss. I hope that my contribution to the lore of useful I7 tricks comes in handy to someone writing the next wordplay classic.

5 Likes

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