Hi Otis, to assist the terminally indolent / cerebrally challenged please can you include a link to the winning post? EDIT: I’ve found it, I think
Oops, sorry – I keep the top post updated with links to each winning post, but it makes sense to include them in the discussion starters down below, too. I’ll edit them in.
Sure thing! I am honored to receive this suspiciously bubbling beaker!
So, Inform has a very elaborate system for printing lists of objects. This is accessed through a phrase with a truly staggering number of options:
To list the contents of (something - an object), with newlines, indented, as a sentence, including contents, including all contents, giving inventory information, giving brief inventory information, using the definite article, listing marked items only, prefacing with is/are, not listing concealed items, suppressing all articles and/or with extra indentation:
Why are all these options necessary? Well, in Inform, descriptions like “angry male people in the Gymnasium” are values in their own right, which we can pass around to phrases as much as we like! So it’s very easy for an author to specify what things should be in their list.
The hard part is specifying what should be printed for each item in the list. That’s why we need options like “suppressing all articles” and “using the definite article”. This is also the difficult part for printing lists of anything that’s not an object.
The solution to this problem came from the Hadean Lands source, where zarf used a text for this! His list-writer sticks the current item into a global variable called “substitution-variable”, and the text you pass into the list-writer can thus reference “[the color of the substitution-variable]” or whatever you like.
Previously, I’d been writing my own little mini-list-writer code every time I needed a customized list. For example, I have a “to (direction)” phrase to handle the difference between “to the north” and “upward” (or, in this particular case, “to starboard” and “forward”!). If I want to say “You can go…” followed by a list of viable directions, using this phrase, I need to write my own loop that prints all the commas in the right places and everything. Or, I have a kind of value called “position”, which has a property called “name” (to handle the fact that “above” already has a meaning to I7). If I want to print a list of positions using their names, again, I need to handle all the commas and “ands” myself.
But with this text trick, suddenly that’s no longer needed!
First, a hack that I’ve used before to access activity parameters. In I6, variables don’t have types. In I7, they do. But in I7, you can pass kind names to phrases! So:
To decide which K is the (name of kind of value K) being listed: (- listing_parameter -).
To set the (name of kind of value K) being listed to (V - K): (- listing_parameter = {V}; -).
So now we have a variable, the “thing being listed” (or “direction being listed” or “position being listed” or “number being listed” or…), which can take on any type we want it to. Sorry, type-checker.
Then, we take in a description and a text:
To print a list of (T - text) for (D - description of values of kind K), prefacing with is/are and/or disjunctive:
Note that “description of values of kind K”. Inform’s type-checker knows that “viable directions” is a description of directions, and “positions accepted by the noun” is a description of positions. So there’s no need for the author to specify that explicitly: Inform tells our phrase what its type-checker knows already!
There are two main things we can do with descriptions, and we’re going to do both:
let N be the number of D;
Find out how many there are, and…
repeat with V running through D:
set the K being listed to V;
say T;
…repeat through them!
The rest is just a bunch of details to get commas in the right places and handle nested lists (instead of clobbering the listing-parameter variable, we push it onto the stack, and pull it back when we’re done). But we can now say:
print a list of “[to the direction being listed]” for viable directions
print a list of “[name of the position being listed]” for positions accepted by the noun
Or anything else, for that matter! Using texts referencing a global variable like this really is a game-changer!
Oh yeah, and an extension version is coming as soon as I’ve stress-tested it a bit more!
This week’s prize, Bubbling Beaker Award® #4, is presented to @evc003 (Eric Conrad), who hasn’t visited the forum in some time. His award-winning post has a practical bent: It demonstrates a method for setting up asymmetrical map connections at compile-time using the relatively-undocumented mapping <direction>
relations. It may not sound like much, but it drew a comment of “Ooh, nice.” from none other than zarf. (Note that there may be tweaks needed in 10.1.2 or future releases. See discussion at https://intfiction.org/t/mapped-above-mapped-below)
It occurs to me that, although the compiler source code was a black box at the time this week’s award-winner was posted, now the source code is open for inspection.
I still don’t know my way around it very well, but I know that @Zed and @Draconis have spent some time reviewing it. Can either of you two shed some light on the dividing line between “simple” and “complex” declarations on which evc003’s technique depends? That would be a good supplement to the discussion on the original post itself.
Some time ago I did some experimenting and observed the following:
Making one-way connections
As noted above, phrases including ‘nowhere’ can be used to override the compiler’s innate tendency to assume two-way connections.
Also, an explicitly stated alternate destination for an assumed reverse mapping direction will take precedence over the assumption:
For example after: The Lawn is east of the Potting shed and west of the Herbaceous Border. East is the Lake.
the Lawn remains west of the Herbaceous Border, but there is no corresponding eastward connection- east of the Lawn is the Lake.
Furthermore, the compiler only creates two-way connections for what the documentation calls ‘simple sentences’ Unfortunately, this cryptic comment leaves it a little vague where the boundary between ‘complicated’ and ‘simple’ lies. The example given in the documentation is:
The Attic is a dark room above the Parlour.
rather than the simple The Attic is above the Parlour
, but actually even The Attic is a room above the Parlour
appears ‘complicated’ enough to create a one-way connection. A dark room above the Parlour called the Attic contains a brass lamp.
also creates a one-way connection.
(Note that the Parlour must have been already declared for the compiler to accept this- otherwise the following complaint is made:
The sentence ‘The Attic is a room above the Parlour’ appears to say two things are the same - I am reading ‘Attic’ and ‘room above the Parlour’ as two different things
Also, The Attic is a room above
or The Attic is a room above it
both provoke a similar complaint even with prior declaration of the Parlour)
Conversely, the apparently equally complicated The Parlour is a room. Above is a room called the Attic
or Above the Parlour is a room called the Attic
or A dark room called the Attic is above the Parlour
or even Above is a dark room called the Attic
all lead to a two-way connection.
The simplest approach to creating explicit one way connections is therefore probably to use the exact forms:
The <name-of-a-new room> is a <optional-list-of-adjectives> room <direction> of/from/-- <name-of-a-previously-declared-room> OR
A <optional-list-of-adjectives> room <direction> of/from/-- <name-of-a-previously-declared-room> called <name-of-a-new-room> contains <list-of-objects>
remembered through the archetypes:
'The Attic is a dark room above the Parlour.' OR 'A dark room above the Parlour called the Attic contains a brass lamp.'
This week’s prize, Bubbling Beaker Award® #5 is presented to @Zed, the mad scientist par excellence. His award-winning post demonstrates use of an anonymous relation set up as a property of an enumerated value as a method of handling color mixing rules. (NOTE: Due to a bug in Inform 6M62, his code causes an interpreter crash on startup if compiled for Glulx using 6M62. It will work if compiled to Z-Machine using 6M62, and works for either VM using 10.1 because the bug has been fixed.)
What makes this invention special is that it is a working implementation that approximates some of the functionality of ternary relations, a feature that many Inform users have wished for in the past. For this reason, by vote of his fellow mad scientists, this is the first award to be granted the coveted distinction of being Peer Revued™.
Congratulations, Zed! You are formally invited for show-and-tell.
Thank you. I’m tickled pink to have been Peer Revued™. But I must confess that I have misgivings that this one was mad enough! Like I said there, something like this technique may be what’s “meant to” be idiomatic Inform!
Relations are really the central organising idea of Inform [emphasis added]
WI 13.14-13.15 and WI 22.1 make clear that the relation of K to L
syntax is a kind-of-value constructor, and one can have values of that kind, and variables that hold values of that kind. And, so, why not properties?
Only problem is: it didn’t actually work. Or so I thought. I only compile to Z-code on rare occasion to test something in particular. Until you told me it worked on the Z-machine with 9.3/6M62, Otis, I had no idea.
It’s easy to point to when I found out that relations as properties worked in 10.1 (on glulx) 'cause I posted about it (in one of the several threads over the years in which someone was yearning for a ternary relation).
(A different example of using a constructed KoV as a property can be seen in an answer to Dynamic relations: what’s the idom? where I made an enumerated kind of value with a thing-based rulebook as a property.)
I guess that link answers the question I posed in the original award-winning thread ‘Can forms of relation other than various to various be usefully used as properties?’
Well, at least partially. I can’t (so far) find a working syntax that explicitly creates for example a various-to-one color to color relation as a property.
Two things:
-
The fact that it works on Z-Machine for 6M62 is pure luck – an oddity of some low-level logic being right enough for the wrong reasons due to an integer overflow that results in a fortuitous value. (Details available by PM, if you care, but the root cause is already fixed in 10.1.)
-
As far as whether it was “mad enough,” I can reveal only that a comment by one of the revue-ing peers deemed it “insane genius,” so I think you’re on firm ground.
I had missed that you did basically the same thing 16 months ago. Better late than never!
As CrocMiam pointed out on the thread from 16 months ago, it’s also possible to approximate ternary relations via a table. But as Dannii pointed out in response, the main drawback of a table is that it has to be sized at compile time and cannot be expanded at run time, so the author must attend to this detail. By contrast, with Zed’s technique, the various relations of colors to colors will all be sized correctly by the compiler.
With a table approach, the author can set up initial relations which will be compiled into the starting state of the table. With Zed’s approach, the author must write a rule to initialize the relations at startup, because all of the anonymous relations begin empty. (It’s about the same amount of typing either way.)
With either approach, the state of pseudo-ternary relations can be modified at run time.
With a table approach, the binding between elements of the tuple is via sharing a row in the table. With Zed’s approach, binding is in two stages: A given second color is bound to the mix result color via a relation assertion, and the first color is bound to a set of relation assertions via the link between that color value and its relation-holding property.
With either approach, phrases must be used to both set and get information from the pseudo-relation.
Overall, the two approaches seem comparable:
Comparison of methods
Color is a kind of value.
The colors are brown, red, orange, yellow, blue, green, indigo, violet.
[Zed's technique]
A color has a relation of a color to a color called the mixmaster.
To (c1 - color) mixed with (c2 - color) is (c3 - color):
now the mixmaster of c1 relates c2 to c3;
now the mixmaster of c2 relates c1 to c3;
[if no relation, decides on default value of color]
To decide what color is (c1 - color) mixed with (c2 - color):
if c2 relates to a color by the mixmaster of c1, decide on the color that c2 relates to by the mixmaster of c1;
When play begins (this is the initialize color mixing relations rule):
[these lines cause crash for Glulx in 6M62]
red mixed with blue is violet;
red mixed with orange is red;
[Table-based implementation]
Table of Color Mixing
color1 (color) color2 (color) result (color)
red blue violet
red orange red
with 50 blank rows.
[if no row, decides on default value of color]
To decide which color is the result of mixing (c1 - color) with (c2 - color):
repeat through the Table of Color Mixing:
if color1 entry is c1 and color2 entry is c2, decide on result entry;
if color1 entry is c2 and color2 entry is c1, decide on result entry;
When play begins (this is the compare techniques rule):
say "red + blue = [red mixed with blue].";
say "red & blue = [result of mixing red with blue].";
say "blue + red = [blue mixed with red].";
say "blue & red = [result of mixing blue with red].";
say "red + orange = [red mixed with orange].";
say "red & orange = [result of mixing red with orange].";
say "orange + green = [orange mixed with green].";
say "orange & green = [result of mixing orange with green].";
… but Zed’s is definitely the “mad science” version.
In celebration of International LISP Day (well, not really- that’s May 4th) - today everything’s a list!
Here’s a method to do the same thing with a list of lists of colours…
Which enables the 3-tuples, e.g. {red,yellow,orange}
(i) to be asserted in initial setup without an initialisation routine (unlike a relation property)
(ii) to be modified at run-time
(iii) to be added to indefinitely at run-time subject to the number of colour-combinations available (unlike a table)
This heavily commented example sets up an intial list of tuples, including some inappropriate duplicates, then manipulates the list through user input.
Musing a little more about the different applications of these methods:
(i) Relations allow some nice built-in phrases such as ‘list of colours to which yellow relates by the mixmaster of blue’
(ii) Tables allow the widest range of different kinds to make up the tuple from (three columns of different kinds), while if using a list of lists all the elements of the tuple must be of exactly the same kind. It would be possible, if a little awkward, to maintain three corresponding lists of different kinds where a tuple is made up of entry x of list 1, entry x of list 2 and entry x of list 3.
This week’s prize, Bubbling Beaker Award® #6 is presented to @BadParser, who is not a mad scientist but seems to take a scientific approach to Inform. BadParser’s award-winning post demonstrates how to skirt the I7 compiler’s refusal to accept an action that applies to two kinds of value (and zero objects) by deploying a general parsing routine modeled after the “third noun” example from the DM4.
BadParser, congratulations! You are invited to share details about inspiration, conception and execution here, if so inclined.
I’ll note that you’re a mad scientist if you say you’re a mad scientist, so maybe BP is a mad scientist and they just haven’t told us yet!
I am truly humbled and not sure how worthy this post is for such an honor, but would like to respond by saying: my inspiration was @raragi’s question (in post #6 of that original thread); my conception was to search this forum; my execution was to slightly modify the code I found.
However, to make my contribution a little more “Beaker-worthy” and, in the spirit of fellowship with past award winners, I have decided to extend the code and combine it with last week’s winner, Zed Lopez’s BBA #5.
This is also an attempt to finally answer Daniel’s question to @zarf from December, 2013:
This implementation is not really complete as a general solution, which is left up to the reader. I may not be a mad scientist, but I do like to drive scientists mad.
"Action with Two Values - Bubbling Beaker Awards Edition" by Mike Tarbert
[NB: does not compile at all in version 6M62; crashes as written here in 10.1.0 (in Borogove app); works as written in 10.1.2. In order to work properly in general using 10.1.2 (or at all in Borogove's 10.1.0) requires voodoo and/or other black magic unbeknownst to this author (or just trace experimentation) to determine the second argument for ParseToken called within SecondColor.]
Lab is a room.
Section 1 - Zed Lopez's Bubbling Beaker Award Winner #5
color is a kind of value.
the colors are brown, red, orange, yellow, blue, green, indigo, violet.
a color has a relation of a color to a color called the mixmaster.
to (c1 - color) mixed with (c2 - color) is (c3 - color):
now the mixmaster of c1 relates c2 to c3;
now the mixmaster of c2 relates c1 to c3;
to decide what color is (c1 - color) mixed with (c2 - color):
if c2 relates to a color by the mixmaster of c1, decide on the color that c2 relates to by the mixmaster of c1;
when play begins (this is the set up colors still on the TODO list rule):
red mixed with blue is violet;
red mixed with yellow is orange;
yellow mixed with blue is green;
Section 2 - BP's BBAW #6 Modified for Use with a New KOV
Include (-
Global second_color;
-).
The second color understood is a color that varies.
The second color understood variable translates into I6 as "second_color". [represented as a number in i6]
Include(-
[ SecondColor x firstnum;
firstnum = parsed_number; ! saves the first parsed number locally
x = ParseToken(GPR_TT, 212542); ! <- I don't know how to calculate this GPR number
if (x == GPR_FAIL or GPR_REPARSE) return x;
second_color = parsed_number; ! saves the second parsed number into a global
parsed_number = firstnum; ! returns the first number to its usual global spot
return GPR_PREPOSITION;
];
-).
The Understand token second-color translates into I6 as "SecondColor".
Section 3 - Mixing It Up
Mixing it with is an action applying to one color.
Understand "mix [color] with [second-color]" as mixing it with.
Report mixing it with:
say "Mixing [color understood] with [second color understood] (in this implementation at least) yields: [color understood mixed with second color understood].".
test me with "mix red with blue / mix blue with red / mix blue with yellow / mix violet with yellow / mix blue with blue / mix blue with vermilion".
I would also like to thank the academy and to tell all you youngsters out there that you never know what a little pouting in the Lounge can get you.
I don’t know how to find this number programmatically (it is the address of the General Parsing Routine (GPR) represented by the [color] token, and as far as I know, inaccessible to I7) but by creating a replica GPR function, we can use the name of that as the argument instead, which is a litlle less ‘voodoo’ than finding and inserting a highly-unstable magic number:
Here is a method to find the address of the GPR routine represented by a token such as [color], by pulling it from the grammar of a verb word that in the first grammar line in its grammar table includes [color] as the first token after the verb word.
In this example, after Understand "mix [color] with [second-color]" as mixing it with.
, the grammar token [color] is indeed the first token after ‘mix’ in a single grammar line, so…
First when play begins: set color token.
To set color token: (- default_color_token=GetGPRRoutine('mix'); -).
Include (-
Global default_color_token;
[ GetGPRRoutine verb_wd i line_address first_ttype first_tdata pcount;
! call this with a verb (dictionary word) and if the 1st token of the 1st grammar line for that verb is a GPR routine, the function returns its address
i = DictionaryWordToVerbNum(verb_wd);
line_address = ((#grammar_table)-->(i+1))+4; ! look up start of grammar table for this verb, skip 1 byte for <no of grammar lines>; 3 for <##action name + flags byte> of first line, to reach first token
first_ttype=line_address->0 & $0F; ! token type (we want GPR_TT)
first_tdata=(line_address+1)-->0; ! token data
if (first_ttype==GPR_TT) return first_tdata; ! first_tdata is address of the GPR routine
print "###Error - GetGPRRoutine() tried to find a GPR routine address from the first grammar token of the verb'", (address) verb_wd, "' but that token wasn't a GPR routine.^^";
! PrintGrammar(verb_wd);
rfalse;
];
-).
which means when play begins, the I6 global default_color_token now contains the address of the GPR routine represented by [color], and we can use that in our second colour token routine, thus:
[ second_color_token x firstnum;
firstnum = parsed_number; ! saves the first parsed number locally
x = ParseToken(GPR_TT, default_color_token); ! <- global assigned when play begins with the address of the [color] token GPR routine
if (x == GPR_FAIL or GPR_REPARSE) return x;
second_color = parsed_number; ! saves the second parsed number into a global
parsed_number = firstnum; ! returns the first number to its usual global spot
return GPR_PREPOSITION;
];