Make parser select unique nouns in multiple-object actions

I have an action that is applied to multiple nouns. In my case, I have a third noun, but all the code to do that is supplied in the example. This is an issue with second nouns just the same. Anyway, the problem is that sometimes I want to have the action performed on multiple instances of the same kind. The key is, each instance needs to actually be a separate instance, not the same one matched repeatedly.

In this silly example, 3 apples are combined to make applesauce. The problem is that the command “combine apple with apple and apple” results in the same apple being selected for the noun, the second noun, and the third noun. Each noun should have to be a different apple. How do I tell the parser a noun has “already been used” and is now “off limits”?

We know this is not happening because after the applesauce is made, only 1 of the apples is moved to the prop-room.

To say the kind of (O - an object): (-
	if ({O} provides KD_Count) {
		print (I7_Kind_Name) (KindHierarchy-->(({O}.KD_Count) * 2));
	} else {
		print "object";
	}
-).

To decide which text is the printed kind of (O - object):
	decide on the substituted form of "[the kind of O]".

The third noun is an object that varies. The third noun variable translates into I6 as "third".

	The understand token third something translates into I6 as "THIRD_NOUN_TOKEN".

	[cf. DM4, p. 489]

	Include (-
	Global third;
	-) after "Definitions.i6t".

	Include (-
	[ THIRD_NOUN_TOKEN  x;
		x  =  ParseToken(ELEMENTARY_TT,  NOUN_TOKEN);
		if  (x  ==  GPR_FAIL  or  GPR_REPARSE)  return  x;
		third  =  x;  return  GPR_PREPOSITION;
	];
	-).

Combining it with is an action applying to two things.

Understand "combine [something] with [something] plus [third something]" as combining it with.

Combining-things is a truth state that varies.

Last every turn while combining-things is true:
	now combining-things is false;

Report combining it with (this is the report combining it with failure rule):
	if combining-things is false:
		say "You can't think of a useful way to combine those items.";
	otherwise:
		now combining-things is false;

After reading a command:
	if the player's command includes "combine":
		if the player's command includes "and":
			replace the matched text with "plus";
			
Check combining it with (this is the combine apples rule):
	if the noun is an apple and the second noun is an apple and the third noun is an apple:
		let L be a list of texts;
		add the printed kind of the noun to L;
		add the printed kind of the second noun to L;
		add the printed kind of the third noun to L;
		sort L;
		repeat through table of applesauce making:
			let x be a list of texts;
			now x is the components list entry;
			sort x;
			if L is x:
				now the noun is in the prop-room;
				now the second noun is in the prop-room;
				now the third noun is in the prop-room;
				repeat with y running through things in the prop-room:
					if "[the kind of y]" exactly matches the text "[result entry]", case-insensitively:
						now y is in the location;
						say "You made an [result entry]!";
						now combining-things is true;
						break;

Table of applesauce making
components list	result
{"apple","apple","apple"}	"applesauce"					

The prop-room is a room.

An applesauce is a kind of thing. 1 applesauce is in the prop-room.

The kitchen is a room.

The player is in the kitchen.

An apple is a kind of thing.

3 apples are in the kitchen.

I had a vaguely similar issue in Tea and Toast with teacups; the teacups were identical and so if you typed “fill teacup” several times Inform would try to fill the first teacup in the object list several times. The way I handled this was not at the parser stage but with a “Before” rule; if Inform was trying to fill up a teacup that wasn’t suitable (already filled, or in a cabinet) it would try to find a suitable teacup and redirect to it.

You might be able to do this if you can find a good way to specify what to redirect to. You know that the second noun is unsuitable if it’s the same as the noun; if you’ve got some way of figuring out what a good substitute for the second noun is, like some code that lets you find an instance of the same kind as the second noun, you can pick an unused one of those. (Checking for visibility and all that.) And so on for the third noun.

There’s also a little used [other things] token mentioned in Writing with Inform 17.4. But it might be hard to make it work; it allows multiple objects and it’s not clear to me how you could make it work with more than two nouns. (The inserting it into action is defined with “put [other things] in/inside/into [something]” so “PUT ALL IN WALLET” doesn’t wind up trying to put the wallet in the wallet.) And the way this token is parsed is reeeaaaaalllly complicated so I wouldn’t try to mess with it, myself.

The easiest way to do it would be to ditch the third noun parsing (it works, but it makes it much harder to write rules and won’t be included in stored actions) and parse “combine [things]” instead, then use the multiple item list in your rules. It ensures that each object is unique, as well as allowing the player to specify as many or as few items as she wants (rather than exactly three, no more, no less).

That too. It also allows the player to type “COMBINE THREE APPLES” or “COMBINE EVERYTHING IN THE BOX” and fun stuff like that.

I tried the code below, but I’m stymied by this:

Combine apple apple apple
You must supply a second noun.

To say the kind of (O - an object): (-
	if ({O} provides KD_Count) {
		print (I7_Kind_Name) (KindHierarchy-->(({O}.KD_Count) * 2));
	} else {
		print "object";
	}
-).

To decide which text is the printed kind of (O - object):
	decide on the substituted form of "[the kind of O]".

Combining it with is an action applying to one visible thing and one visible thing.

Understand "combine [things]" as combining it with.

Combining-things is a truth state that varies.

Last every turn while combining-things is true:
	now combining-things is false;

Report combining it with (this is the report combining it with failure rule):
	if combining-things is false:
		say "You can't think of a useful way to combine those items.";
	otherwise:
		now combining-things is false;

After reading a command:
	if the player's command includes "combine":
		if the player's command includes "and":
			replace the matched text with "plus";
			
Check combining it with (this is the combine apples rule):
	let L be the multiple object list;
	let combining-apples be a truth state;
	repeat with check-type running through L:
		if check-type is not an apple:
			now combining-apples is false;
	if combining-apples is true:
		let text-L be a list of texts;
		repeat with text-shunt running through L:
			add the printed kind of text-shunt to text-L;
		sort text-L;
		repeat through table of applesauce making:
			let x be a list of texts;
			now x is the components list entry;
			sort x;
			if text-L is x:
				repeat with y running through things in the prop-room:
					if "[the kind of y]" exactly matches the text "[result entry]", case-insensitively:
						now y is in the location;
						say "You made [the printed name of y]!";
						now combining-things is true;
						repeat with used running through L:
							now used is in the prop-room;
						break;

Table of applesauce making
components list	result
{"apple","apple","apple"}	"applesauce"					

The prop-room is a room.

An applesauce is a kind of thing. 1 applesauce is in the prop-room.

The kitchen is a room.

The player is in the kitchen.

An apple is a kind of thing.

3 apples are in the kitchen.

You have the action defined as applying to two visible things. You should change that to one [touchable] thing.

Aha! Ok, I still have problems with my rule, but at least it is registering the nouns now, so I’ll try to work the details out myself before asking you good folks for help again. Thanks!

Ok, I wasn’t gone that long :stuck_out_tongue:

But anyway, I have this working, essentially, but I don’t understand why this is trying to do the function for each item in the list. The function is meant to be performed on “the list” as a unit. This output looks as though it is being attempted once for each item in the list, which is not what I want to happen.

>combine apples
You made applesauce!

You can't reach into the prop-room.
You can't reach into the prop-room.

I thought I was on to something when I found the announce items from multiple object lists rule, but even then, I don’t want it just to not say things for each item, I need it to not do things for each item, but the list of items once as a whole. If it tries to do something to each item, this could be very problematic with other rules I have envisioned that will be interrupting this process in the future to determine different kinds of success values.

To say the kind of (O - an object): (-
	if ({O} provides KD_Count) {
		print (I7_Kind_Name) (KindHierarchy-->(({O}.KD_Count) * 2));
	} else {
		print "object";
	}
-).

To decide which text is the printed kind of (O - object):
	decide on the substituted form of "[the kind of O]".

Combining it with is an action applying to touchable things.

Understand "combine [things]" as combining it with.

Combining-things is a truth state that varies.

Last every turn while combining-things is true:
	now combining-things is false;

Report combining it with (this is the report combining it with failure rule):
	if combining-things is false:
		say "You can't think of a useful way to combine those items.";
	otherwise:
		now combining-things is false;

The announce items from multiple object lists rule is not listed in any rulebook.
			
Check combining it with (this is the combine apples rule):
	let L be the multiple object list;
	let combining-apples be a truth state;
	now combining-apples is true;
	repeat with check-type running through L:
		if check-type is not an apple:
			now combining-apples is false;
	if combining-apples is true:
		let text-L be a list of texts;
		repeat with text-shunt running through L:
			add the printed kind of text-shunt to text-L;
		sort text-L;
		repeat through table of applesauce making:
			let x be a list of texts;
			now x is the components list entry;
			sort x;
			if text-L is x:
				repeat with y running through things in the prop-room:
					if "[the kind of y]" exactly matches the text "[result entry]", case-insensitively:
						now y is in the location;
						say "You made [the printed name of y]!";
						now combining-things is true;
						repeat with used running through L:
							now used is in the prop-room;
						break;

Table of applesauce making
components list	result
{"apple","apple","apple"}	"applesauce"					

The prop-room is a room.

An applesauce is a kind of thing. 1 applesauce is in the prop-room.

The kitchen is a room.

The player is in the kitchen.

An apple is a kind of thing.

An orange is a kind of thing.

3 apples are in the kitchen.

1 orange is in the kitchen.

The default behavior is like what happens when you “get all”. The easiest way to override it for a specific action is shown in “The Left Hand of Autumn”. If you’re going to have lots of verbs like this I’d recommend creating a category for such actions (“combining something is grouped action”) or adding a rulebook to your replacement for the announce… rule.

I’m glad to learn another thing that escaped me in reading on my own… that you can create groups/kinds of actions. Thanks for that tip.

I’m still struggling with this part though. I had added this:

Combining-things is a truth state that varies.

Before reading a command:
	now combining-things is false;

Check combining it with when combining-things is true (this is the stop repeat combinations rule):
	stop the action. 
	
The stop repeat combinations rule is listed first in the check combining it with rulebook.

But this doesn’t seem to stop it from attempting the action on each item. Is that not the part that is supposed to govern that behavior?

I don’t know if this is the issue, but if combining only applies to one thing you don’t need to call the action “combining it with.” The “it” tells Inform where the noun goes (and the second noun goes after the action name), so just calling it “combining” should be fine.

Also, are you sure “combining-things” is getting set to true? It’s under a lot of conditions; you might want to try turning on rules debugging just to make sure the rules are getting run in the right order.

Try this.

Combining is an action applying to one thing. Understand "combine [things]" as combining.

Combination-cancelled is initially false.

Check combining something: if combination-cancelled is true, stop the action.

Carry out combining something:
    say "Combining [the multiple object list].";
    [do other stuff here;]
    now combination-cancelled is true.

Every turn: now combination-cancelled is false.

The announce items from multiple object list rule does nothing when combining.

I essentially had that, but please know that I am sincere, not being ungrateful, when I say that the code you provided has helped me learn a little better some ways to write more concise and clear code in Inform. Thank you for that again. Still, fundamentally, this hasn’t changed what’s going on.

I hate to keep just posting each iteration of this, but this makes no sense.

To say the kind of (O - an object): (-
	if ({O} provides KD_Count) {
		print (I7_Kind_Name) (KindHierarchy-->(({O}.KD_Count) * 2));
	} else {
		print "object";
	}
-).

To decide which text is the printed kind of (O - object):
	decide on the substituted form of "[the kind of O]".

Combining is an action applying to one thing. Understand "combine [things]" as combining.

Combination-cancelled is initially false.

Check combining something: if combination-cancelled is true, stop the action.

Carry out combining apples:
	now combination-cancelled is true;
	say "Combining [the multiple object list].";
	let L be the multiple object list;
	let text-L be a list of texts;
	repeat with text-shunt running through L:
		add the printed kind of text-shunt to text-L;
	sort text-L;
	repeat through table of applesauce making:
		let x be a list of texts;
		now x is the components list entry;
		sort x;
		if text-L is x:
			repeat with y running through things in the prop-room:
				if "[the kind of y]" exactly matches the text "[result entry]", case-insensitively:
					now y is in the location;
					say "You made [y]!";
					repeat with used running through L:
						now used is in the prop-room;
					break;
			break;

Every turn: now combination-cancelled is false.

The announce items from multiple object lists rule does nothing when combining.

Table of applesauce making
components list	result
{"apple","apple","apple"}	"applesauce"					

The prop-room is a room.

An applesauce is a kind of thing. 1 applesauce is in the prop-room.

The kitchen is a room.

The player is in the kitchen.

An apple is a kind of thing.

3 apples are in the kitchen.

I turned on rule tracing, and it looks like the combining apples is only happening once, but the before stage rule is triggering during the repeat loop that is removing the items from the room. I don’t understand why, as no interaction is being initiated against them if the combining rule isn’t being applied to them multiple times is there? They very much need to be removed from the room as a function of the combining rule, as they are being used up in the combination.

kitchen
You can see three apples here.

>rules
Rules tracing now switched on. Type "rules off" to switch it off again, or "rules all" to include even rules which do not apply.

>combine apples
[Rule "declare everything initially unmentioned rule" applies.]
[Rule "set pronouns from items from multiple object lists rule" applies.]
[Rule "before stage rule" applies.]
[Rule "instead stage rule" applies.]
[Rule "investigate player's awareness before action rule" applies.]
[Rule "player aware of his own actions rule" applies.]
[Rule "check stage rule" applies.]
[Rule "Check combining something" applies.]
[Rule "carry out stage rule" applies.]
[Rule "Carry out combining apples" applies.]
Combining apple, apple and apple.
You made applesauce!
[Rule "after stage rule" applies.]
[Rule "investigate player's awareness after action rule" applies.]
[Rule "report stage rule" applies.]
[Rule "last specific action-processing rule" applies.]
[Rule "set pronouns from items from multiple object lists rule" applies.]
[Rule "before stage rule" applies.]
You can't reach into the prop-room.
[Rule "set pronouns from items from multiple object lists rule" applies.]
[Rule "before stage rule" applies.]
You can't reach into the prop-room.

[Rule "A first turn sequence rule" applies.]
[Rule "every turn stage rule" applies.]
[Rule "Every turn" applies.]
[Rule "A last turn sequence rule" applies.]
[Rule "notify score changes rule" applies.]
>

I don’t get it.

Ah, I think it’s that the basic accessibility rule is messing you up even before the before rules run. When Inform starts to think about how to process the “combining the apple” action and the apple is in the prop room, the first thing it does even before running the before rules is to see if the player can reach the prop room – which they can’t – and print a refusal. In order to let that case fall through to your Check combining rule, you could add this:

Rule for reaching inside the prop-room when combining: if combination-cancelled is true, allow access.

[rant=not really true](The basic accessibility rule can be very annoying like that, since it really doesn’t want to let any other rules run, and it doesn’t show up on rules tracing; I had to turn on actions tracing as well to figure out exactly what was going on, although the “can’t reach inside” message should’ve told me.)[/rant]

ETA: Hmm, sections 12.16 and 12.2 imply or outright say that the basic accessibility rule should be running between Before and Instead… oh, ah, you have it as a check combining rule! Change the rule that stops the action when combination-cancelled is true from a Check combining rule to a Before combining rule and it will work without your needing to write a reaching inside rule.

ETA 2: This actually accords with what Writing with Inform says about writing rules for actions in 12.21: Before rules are good for diverting actions before they even get underway. Check rules can work to stop an action that’s being tried but failing, but Before rules work to stop an action before you can even think about doing it as it were.

Holy… wow, thanks again! That is gnarly and confusing! I changed it from check to before, and it works. It’s just kind of mind twisting to think that a rule is running “before” itself in the middle of its own execution… phew.

Well, the rule isn’t running before itself. What happens is that all the rules run for combining the first apple, and for combining the second apple, and then for combining the third apple. Looking at outdated* but quite handy chart, you’ll see (down in the bottom half) that the basic visibility rules and the basic accessibility rules run in between the Before and Instead rules. This means that, once the action has got through the Before stage, it checks to see if the player can see or touch (if the action requires a touchable noun) the noun/second noun. Often objects that aren’t touchable wouldn’t be parsed – if the apple had started in the prop-room when Inform was processing the command into actions then it wouldn’t have been able to parse “combine apple” into the action of combining this apple – but in this case the apple was in scope when the command was parsed, but it’s not in scope anymore, since the action of combining the first apple moved all three to the prop-room. So the basic accessibility will cut off the action if it’s allowed to run.

BUT, since the Before rules run before the accessibility checks, if we cut the action off in the Before rules we don’t have to worry about accessibility.

The tricky thing is that the accessibility rules don’t show up in the Rules tracing; as I said you may have to have Actions tracing on to verify that an action is failing because of the basic accessibility rule.

*Offhand the only anachronism I see is that it mentions procedural rules, which don’t exist anymore, but there might be others.