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

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

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