Writing A New [one of] Construction (Inform 7)

First some background. I’m working on a piece (that will probably never come to light but is fun to write anyway) where there are changing lipogrammatic constraints. Different letters can be declared forbidden and as long as they remain forbidden, they won’t appear in the output. Of course, this means that I’m writing dozens of alternate versions of every piece of text that will be written to the screen. (Yes, this is an enormous amount of work, but fortunately writing the alternate versions of the text is the fun part for me, so I don’t mind.) I’ve got the necessary framework set up to do this (basically, I just end up writing tables of alternates in decreasing order of preference). But I would like to use some substitutions to make the process just a smidgen easier.

In particular, I would like to have a construct similar to the many variations of [one of] that are available where I can list a few versions of a piece of text and have it evaluate to the first version that fits the current constraint. Ideally, I would like to write something like “You [one of]take[or]get[or]pick up[as permitted] [the noun].” It would determine the first of the three options that is “permitted”, as defined elsewhere in the code (see below), choosing the last alternative if none of the options is permitted.

Near as I can tell, the only way to do something like this is to use segmented substitutions and Inform 6, roughly as described in section 27.30 of the documentation. I feel like I’ve got enough of a feel for how that’s supposed to work to do something that chooses a variation based solely on the position of the options (e.g., “under circumstance X, choose option number N”), but I need to actually work with the content of each option (e.g., “for each option N, if the text of that option satisfies property P, choose it, otherwise, move on to option N+1”).

Given that my I6 skills are pretty weak, how hard/complex would something like this be? Is there a better/simpler/more-I7 way to do it?

The exact code I’m using to determine whether text is “permitted” is probably irrelevant, but here it is:

[code]The list of forbidden letters is a list of text that varies.

To decide whether (X - some text) is forbidden:
repeat with L running through the list of forbidden letters:
if the substituted form of X matches the text L, case insensitively:
yes;
no.

To decide whether (X - some text) is permitted:
if X is forbidden, no;
yes.

To forbid (X - some text):
add X to the list of forbidden letters.

To permit (X - some text):
remove X from the list of forbidden letters, if present.
[/code]

I’ve read a bit more, and now I’m wondering if this is doable at all.

So if I’m going to follow the pattern from 27.30 in the documentation, I’d want to write something like this:

To say as permitted -- ending say_one_of with marker I7_SOO_PER:
   (- {-close brace} -).

Include (-
[I7_SOO_PER oldval count;
 ...];
-)

But when defining the routine I7_SOO_PER, the only info I can work with is which option was chosen last time and the total number of options. Is there a way to do this so that I have access to the content of the option?

What about filtering your output through a regular expression before printing it? Ron Newcomb’s Output Filtering (inform7.com/extensions/Ron%20New … doc_0.html) made this easy, but I doubt that it works beyond version 6G60. Still, tinkering away with that and Text Capture by Eric Eve (which has been updated) will probably get you further faster than the direction you’re currently exploring.

No, the say-one-of construction doesn’t do that.

You could use Text Capture, or write an explicit phrase like

To permit (X - text) or (Y - text) or (Z - text) else (W - text): …

(Or maybe work on a list of texts.)

Filtering the output wouldn’t be enough for what I’m trying to do. When “e” is forbidden, I don’t want “You get the stone.” to come out “You gt th ston.” I want it to come out “You pick up that rock.” So while tools that directly modify the output after it has been generated and just before it goes to the screen aren’t really what I’m looking for.

The “explicit phrase” you suggested is very similar to the first thing I tried, but while you can define a phrase to say something including text variables, I don’t think you can embed literal text inside a bracketed text substitution. For example, I can write:

To say the text (X - text) loudly: say X in upper case.

And that allows me to write:

Say the text "hello." loudly.

But it doesn’t allow me to write:

Say "[the text]hello[loudly]."

And the following will just never compile:

Say "[the text "hello" loudly]."

As near as I can tell, there’s no way to do segmented substitutions without including some I6 code. As you say, I don’t think it’s possible to do this by adding a new ending for [one of]. I might be able to define a new beginning, continuing, and ending set of substitutions (e.g., [appropriately one of], …) but I’m starting to think that might be a can of worms too big for my attention span these days. Still, it might be the only way to get what I want.

As for working with lists… can you use hard-coded lists of texts as part of a text substitution? E.g., “You [first permitted from {“get”,“take”,“pick up”}] [the noun].”? I feel like the nested quotation marks might cause trouble.

I could certainly define the lists separately and give them a name or something, but that’s not a step up from just defining a special substitution (e.g. “You [get-take-pickup] [the noun].”) for every single possible list of options. In any case, it’s not substantially different from what I do now in the more general case using tables.

Just to make it clearer what I’m looking for, let me show you the code I’m currently using for determining which response to print:

The list of forbidden letters is a list of text that varies.

To decide whether (X - some text) is forbidden:
	repeat with L running through the list of forbidden letters:
		if the substituted form of X matches the text L, case insensitively:
			yes;
	no.

To decide whether (X - some text) is permitted:
	if X is forbidden, no;
	yes.

To forbid (X - some text):
	add X to the list of forbidden letters.

To permit (X - some text):
	remove X from the list of forbidden letters, if present.

To say an appropriate response from (response table - a table name):
	let N be 1;
	choose row N in the response table;
	while the Message Text entry is forbidden:
		increase N by 1;
		choose row N in the response table;
	say Message Text entry.

The parser error internal rule response (T) is "[an appropriate response from the Table of parser error internal rule responses (T)]".

Table of parser error internal rule responses (T)
Message Text
"You can't begin with a comma."
"You can't start with a comma."
"A comma can't begin a command."
"A comma can't start a command."
[...and so on...]

I’m looking for a way to easily build on the fly new sets of alternatives, so that I can include in the table something like “You [first permitted]can’t[or]shouldn’t[end] [first permitted]begin[or]start[end] [first permitted]with[or]using[end] a comma.” Currently, I’d either have to write 8 different entries in the table or effectively create custom shortcuts for [can’t-shouldn’t], [begin-start], and [with-using].

That’s correct. You could define the texts as constants:

Hello-text is always "hello".

Every turn:
	say "[the text hello-text loudly]."

But that takes as much space to set up as you’re saving with the substitution.

You can do something like what you want with Text Capture and with a sort of ersatz segmented substitution (I copied the idea from Erik Temple’s Inline Hyperlinks). The basic idea is that it provides a phrase called “start capturing text” that starts printing the text to a buffer rather than the screen, and one called “stop capturing text” that, well, stops it. So you can define the pieces of your substitution to start capturing text at the beginning, and to check for permissibility at every subsequent one:

Include Text Capture by Eric Eve.

The permitted text is a text that varies.
Permission granted is a truth state that varies.

To say one of perhaps:
	now the permitted text is "";
	now permission granted is false;
	start capturing text.

To say or maybe:
	stop capturing text;
	if permission granted is false: [if permission granted is true, then we've already found a permissible form, and we're just going through the rest of the substitution capturing text so we don't print it by accident. So we skip the next bit. Otherwise, we look at what we've captured for permissibility.]
		now the permitted text is the substituted form of "[captured text]";
		if the permitted text is permissible:
			now permission granted is true; [yay!]
	start capturing text. [note that whatever happens, we want to start capturing text again, so we don't print anything by accident. Also, the way Text Capture works, this should automatically clear the captured text buffer.]
	
To say as permitted: [last chance]
	stop capturing text;
	if permission granted is false:
		now the permitted text is the substituted form of "[captured text]";
		unless the permitted text is permissible: [nothing was permitted; ETA if you want, as you said, to print the last element of the substitution regardless of permissibility you can just drop this check]
			now the permitted text is "Error! Tried to print an impermissible text.";
	say "[permitted text]". [and at the end we actually say it]
	
The forbidden letter is a text that varies. The forbidden letter is "".

Singing is an action applying to nothing. Understand "sing" as singing.

Instead of singing when the forbidden letter is "":
	say "You sing an A so wobbly that the powers that be excise that letter from the alphabet.";
	now the forbidden letter is "a".
Instead of singing when the forbidden letter is "a":
	say "You sing an E so wobbly that the powers that be excise that letter from the alphabet.";
	now the forbidden letter is "e".
Instead of singing when the forbidden letter is "e":
	say "You sing a tolerable scale and the powers that be restore all letters.";
	now the forbidden letter is "".	
	
To decide whether (string - a text) is permissible:
	if the forbidden letter is "", yes;
	if string matches the text "[the forbidden letter]", case insensitively:
		no;
	yes.
	
After taking something: say "[one of perhaps]Taken[or maybe]Gotten[or maybe]Had[as permitted]."

Oulipo is a room. A rock is in Oulipo.

test me with "take rock/drop it/sing/take rock/drop it/sing/take rock/drop it/sing/take rock".

(Extending the expungement to everything besides the “Taken” message is an exercise left to the reader.)

Note that this isn’t a real segmented substitution, because you don’t need to keep track of what got printed last. But if you don’t pair things up properly things will get baaadly messed up. So you might want to define it as a segmented substitution just to make sure that if you miss one of them out it fails to compile instead of producing terrible bugs.

…and now you have to finish the piece, because I really want to play it.

Matt W:

This looks like it will work quite nicely. Especially since I have no need to keep track of the previously displayed text or to look ahead at the other options while deciding to use the current option.

(I guess I was wrong to dismiss Text Capture so quickly. Sorry to everyone else who suggested it.)

If I’ve ever got something playable, I’ll be sure to mention it here. But don’t hold your breath. Even though the game I have in mind is absurdly short, it’s still an immense amount of work.

Oh and thanks for all the help, everyone!

Have you thought about crowdsourcing some of the lipograms? It seems like you could accomplish a lot by getting people to write lipogrammatic forms for some of the messages–your collaborators wouldn’t have to do much with the main program, they could just work on some phrases and send you back their segmented substitutions. I know you said writing the lipograms was the fun part but it might be too much fun if you had to do it all.

What I’m saying is that I’d be happy to help write some lipograms (and maybe some other stuff) but I’m a bit of a flake as far as collaborating goes so it’d also be a good idea to find some other folks to contribute, if you want to do that.

I’d also be happy to chat about design issues if you want.

I might consider crowdsourcing when the whole thing is more stable. Given the amount of free time I have during the semester (as a teacher), it might be quite some time before that happens. I’ll post again here if I ever get to that point.