Commanding multiple actors at the same time

Borrow it all, please!

Wow, now that I’ve looked at the Art of Fugue source code the “for reading a command” hack was far easier than I expected – I was expecting to have to do some crazy I6 magic but literally changing the word “After” to “For” worked. If we make commanding an action out of world we have to change the “Before commanding” rule to a “Check commanding” rule but that should be OK.

There’s still the problem about having “You can only have something animate do something” printing a huge number of times, but that’s solvable, and the issue about how to process “poet and waldo, kiss iris” into “have poet and waldo kiss iris,” but we’re a lot farther toward getting this done than I expected.

So here’s the current (6G60) code, with the business about the command prompt removed since we don’t need it anymore:

[spoiler][code]Include Editable Stored Actions by Ron Newcomb.

The list of commanded actors is a list of persons that varies. The list of commanded actions is a list of stored actions that varies.

Commanding is an action out of world applying to one visible thing and one topic. Understand “have [things] [text]” as commanding. [Basically, when the player types this, we store the list of people in one list and the command that was typed in a text. Then the next turn, no matter what’s typed that turn, we parse this typed command and apply it to all the people in our list. It’s an action out of world so it doesn’t take a turn; the turn happens when we execute the next command.]

Executing a multicommand is a truth state that varies. Executing a multicommand is usually false. [A flag that tells us whether we’re in the mode where we’re about to throw out whatever is typed, parse the command we stored last turn, and apply it to all those people.]
The multicommand text is an indexed text that varies.

Check commanding when executing a multicommand is false:
repeat with item running through the multiple object list: [the multiple object list is the list of things that were processed by a “things” command]
if item is not a person, say “You can only have something animate do something.” instead. [If we let things that weren’t animate into the list of things that were going to perform the action next turn, it would be bad. This can wind up printing the error message far too many times; if we were going to make this work for real we’d want to make sure it only printed once a turn.]

Disambiguation in progress is a truth state that varies.

Carry out commanding when executing a multicommand is false: [This sets up all the stuff we need to actually have the actions happen next turn]
now executing a multicommand is true; [so we know that next turn we do the stuff]
now the list of commanded actors is the multiple object list; [this is the list of things that were understood by the [things] token]
now the multicommand text is “[the topic understood]”; [this is the text understood by the text token; so when the player types “have sam and dave soothe me” the multiple object list is {Sam, Dave} and the topic understood is “soothe me”]
now disambiguation in progress is false.

For reading a command when executing a multicommand is true and disambiguation in progress is false:
change the text of the player’s command to “[the multicommand text]”. [This changes the text of the player’s command to whatever we stored last turn. Then we can parse it as a command. Reparsing the command in one turn would take some crazy parser hacking that I’m not at all capable of. And since this is the “for reading a command” rule, this has essentially taken over the loop where the game asks for a command and processes it, so the player won’t see that this is taking place on a different turn.]

First before when executing a multicommand is true:
now executing a multicommand is false; [this makes sure we go back into ordinary interaction mode]
now the list of commanded actions is {}; [now we make a list of actions to execute]
repeat with the agent running through the list of commanded actors:
let the temporary action be a stored action;
now the temporary action is the current action; [the one that got parsed from the command]
change the request part of the temporary action to true;
change the actor part of the temporary action to the agent; [we’ve used the Editable Stored Actions extension to turn the action that we originally processed from something that the player would do into something that the agent would do, and we’ve set a flag so that it gets processed as a request from the player rather than as an autonomous action by the agent]
add the temporary action to the list of commanded actions; [so that there’s one action on the list for every commanded actor]
repeat with the happening running through the list of commanded actions:
try the happening; [now we’re actually carrying out all those requests]
stop the action. [this prevents Inform from actually trying out the action we parsed]

First check commanding when executing a multicommand is true: stop the action. [This means that when we’re running through the multiple object list after entering the “Have Sam and Dave soothe me” command we don’t go through the previous rule and prematurely reset the executing a multicommand flag]

This is the announce items from multiple object lists when not commanding rule:
if not commanding, follow the announce items from multiple object lists rule.
[Ordinarily when you do actions on multiple items, the announce items from multiple object lists rule says “Rock: Taken. Roll: Taken.” or something like that. We don’t want that to happen when we’re commanding so we write a rule that only prints that text if we’re not commanding. If we were in 6L02 there would be an easier way to do this.]

The announce items from multiple object lists when not commanding rule is listed instead of the announce items from multiple object lists rule in the action-processing rules.

Before asking which do you mean when executing a multicommand is true:
now disambiguation in progress is true.

Persuasion rule: persuasion succeeds. [This just means that people will try to do things we ask them to.]

[and here’s some stuff for them to do. not sure why I put in all these rooms]
The Command Center is a room.
A robot is a kind of person. Understand “robot” as a robot. Understand “robots” as the plural of a robot.
A persuasion rule: persuasion succeeds.
Auda, Iris, Sensa, Waldo, Whiz, and Poet are robots in the command center.

A marble is in the command center. The box is a container in the command center.
A leaflet is a kind of thing. Six leaflets are in the command center.

A brown hat is in the command center. A brown shoe is in the command center.

A room called the Weather Monitors is west of the command center.

A room called the Transit Monitors is south of the command center.

A room called the Hydroponics Monitors is east of the command center.

Instead of an actor kissing Iris:
say “Iris slaps [the actor].”;
rule succeeds.

Instead of Waldo kissing Iris:
say “Iris says, ‘oh you!’”;
rule succeeds.

Test me with “have poet and waldo kiss iris/have all robots get leaflet/ auda, get marble/have all robots put marble in box/whiz, get marble/have all robots put marble in box/auda, get leaflet/iris, get leaflet/poet, get leaflet/have auda and iris and poet and sensa put leaflet in box/have all robots drop leaflet”.
[/code][/spoiler]

Now this is still going to require some twiddling to make sensible things happen with multiple objects of the same kind; it’s still the case that “drop leaflet” has all the robots drop the same leaflet. Though hmmm, maybe we could make the reading a command activity do “auda, drop leaflet” etc. and parse them all in turn. I guess that’s what Art of Fugue does.

In “Suspended”, the only game I know of which uses something like this, the implementation seems to be something like this:

  • Does the command match “both [actor1] and [actor2], [verb] [noun]”?
  • Is the verb “move”? (verbs other than “move” get an error from the FCs)
  • Is the noun the one object in the game for which this syntax works? (nouns other than that one get an error from the FCs)
  • Can actor1 see noun?
  • Set a flag, then have actor2 try to move the noun.
    This leads to some odd glitches, such as “BOTH SENSA AND SENSA, MOVE IT” being accepted (it never checks whether actor1 and actor2 are the same). And since the action is only carried out once, it doesn’t really make sense except for the one time in the game this syntax is needed. (If I were designing it, I would have simply disallowed taking things from the cabinet unless Sensa is “holding” the flowswitch in place, thus requiring two robots without introducing awkward new syntax.)

About parsing it once verses parsing it multiple times: I suppose it really comes down to what behavior you want for ambiguous commands. In a game with lots of timed sequences it might be fairer to only parse once, so that a clever player can’t “cheat” by causing multiple disparate actions on the same turn. But in a game where the actors can be in different rooms, parsing only once could lead to a lot of “can’t reach into…” errors.

Okay, this was much messier than I had expected it to be. But here’s an implementation that parses each command separately. If you include my extension “Modified Timekeeping” a multi-command takes only one turn, as it ought to, rather than one turn for each actor.

Include Editable Stored Actions by Ron Newcomb.

Commanding it to is an action out of world applying to one visible thing and one topic. Understand "tell [things] to [text]" or "command [things] to [text]" as commanding it to.

The multicommand text is text that varies. The multicommand actor list is a list of objects that varies. The multicommand action list is a list of stored actions that varies. The multicommand flag is initially false. The multicommand disambiguation flag is initially false. The multicommand stored actor is initially nothing.

Check commanding someone to a topic when the multicommand flag is true (this is the multicommand mutex rule): stop the action.

Carry out commanding someone to a topic when the multicommand flag is false (this is the set up multicommand list rule):
	if the multiple object list is empty: [This is empty if only one noun was specified.]
		now the multicommand actor list is {};
		add the noun to the multicommand actor list;
	otherwise:
		now the multicommand actor list is the multiple object list;
	now the multicommand text is the substituted form of "[topic understood]";
	now the multicommand stored actor is the player;
	now the multicommand flag is true.

[Rule for reading a command when the multicommand flag is true (this is the replace command with multicommand rule):
	let T be text;
	repeat with the subject running through the multicommand list:
		let U be T; [Store it separately for a moment.]
		let T be the substituted form of "[U][subject], [multicommand text]. "; [Add the subject to the command, so "TELL ALICE AND BOB TO PUSH THE BUTTON" turns into "ALICE, PUSH THE BUTTON. BOB, PUSH THE BUTTON. "]
	change the text of the player's command to T;
	say "([T])[command clarification break]";
	now the multicommand flag is false.]

Rule for reading a command when the multicommand flag is true and the multicommand disambiguation flag is false (this is the replace command with multicommand rule):
[	say "DBG: changing to [entry 1 in the multicommand actor list].";	]
	now the player is entry 1 in the multicommand actor list;
[	say "DBG: changing command to [multicommand text].";	]
	change the text of the player's command to the multicommand text.

Before asking which do you mean when the multicommand flag is true (this is the multicommand begin disambiguation rule): [So as not to replace commands while disambiguation is in progress...]
	say "(for [the printed name of player])[command clarification break]";
	now the multicommand disambiguation flag is true.

After reading a command when the multicommand disambiguation flag is true (this is the multicommand end disambiguation rule):
	now the multicommand disambiguation flag is false.

First before doing anything when the multicommand flag is true (this is the cut off multicommand actions rule):
	let the parse result be a stored action;
	now the parse result is the current action;
	now the actor part of the parse result is entry 1 in the multicommand actor list;
	now the request part of the parse result is true;
	add the parse result to the multicommand action list;
[	say "DBG: removing [entry 1 in the multicommand actor list].";	]
	remove entry 1 from the multicommand actor list;
[	say "DBG: changing back to [multicommand stored actor].";	]
	now the player is the multicommand stored actor;
	if the multicommand actor list is empty: [Time to execute these actions!]
		repeat with the order running through the multicommand action list:
		[	say "DBG: trying [the order].";	]
			try the order;
		now the multicommand actor list is {};
		now the multicommand flag is false;
		silently try waiting; [To make one turn go by.]
[	say "DBG: stopping.";	]
	stop the action.

The announce items from multiple object lists rule does nothing when the multicommand flag is true.

Note that this is 6L02 code, but it would be much the same in 6G60. My first attempt is commented out; it would have been much easier, but Inform parses a command like “ALICE, JUMP. BOB, JUMP.” as “asking alice to try jumping” followed by “answering alice that ‘bob, jump’”.

This causes an infinite loop if I “tell Alice to tell Bob to jump”, due to the mutex. Is there a good way to avoid this?

I don’t have time right now to readapt this to 6G60 – what does an “actions” trace get you in the infinite loop case? Is it getting turned into actions or what?

Alice keeps trying to command Bob to perform an action, but the mutex blocks it (because there’s already a commanding action going on), so the Before rule is never reached, so the mutex is never released.

Can you just set a flag so the mutex rule doesn’t fire when the actor/person asked isn’t the player? Or is actor/person asked not set for an action out of world?

If actor/person asked isn’t set for an action out of world, maybe it’d be better to ditch the “action out of world” stuff and use Modified Timekeeping to prevent the extra turn from being taken.

(It’s also worth thinking about how this will handle Undo, though one approach would just be to forbid undoing this stuff.)

Also on the other stuff I was envisioning multiparsed commands so you could do things like command every child to take a lunchbox, or >ALL N LISTEN FOLKS, DRAW SWORD WAVE, though in that case it’s not totally clear whether separate objects are called for (surely the swords are implemented separately, but is the “sword wave” just a kind of value?)

Here’s a rule that allows BOTH SENSA AND WALDO, MOVE FRED and ALL ROBOTS, REPORT:

After reading a command (this is the modified multicommand parsing rule):
	if the player's command includes "both" or the player's command includes "all": [This check is faster than attempting a regex on every command.]
		let T be "[the player's command]";
		replace the regular expression "^(both|all)\s((\w|\s)+)\," in T with "tell \2 to";
		change the text of the player's command to T;
		[say "DBG: modified command to '[T]'."]

It still can’t handle WALDO, IRIS, AND POET, GO TO SUB SUPPLY (or anything with commas in it, really), so you need to use the TELL syntax for that.

I’ve also found a solution to the “leaflet problem”, but it requires Modified Timekeeping to work. Is that acceptable?

EDIT: So ALL N LISTEN FOLKS, DRAW SWORD WAVE can now be parsed, with the proper Understand lines! (It becomes TELL N LISTEN FOLKS TO DRAW SWORD WAVE, which becomes DRAW SWORD WAVE for each northern listener.)

All rootie! Now we just need to implement the rest of that game.

EDIT: Is there a way to catch IRIS AND WALDO, MOVE FRED? Maybe looking for “and” before the comma, although I guess that’s slow?

If I try implementing this myself I think my solution might be just to convert everything of the form “foo, bar” to “tell foo to bar” and then redirect parser errors at the commanding stage to the answering it that expression, or if the solution to the leaflet problem just turns it back into “foo, bar” then that’ll be fine (as long as I don’t create an infinite loop, heh heh heh). Can’t speak for dootdoot but for any purposes I might have depending on Modified Timekeeping probably wouldn’t be a problem – where can I grab it?

That would work, but I’m not sure how to accomplish it–both the actor section and the action section can contain multiple commas, so how to find the right one? It will probably require some I6 hacking.

Modified Timekeeping is on GitHub here.

Well, I can this of two solutions: either pick the last comma (when would the action section have commas?) or politely tell the player not to use multiple commas. Or there could be a blend, where we pick the last comma and if the action winds up failing to parse (and the original command contains multiple commas) we politely tell the player not to put a comma in a multiparsed command.

Thanks for Modified Timekeeping! I was wondering if this could also be compatible with Variable Time Control, but it looks like in Variable Time Control even actions that take no time run the every turn rules, so that wouldn’t be useful here.

I was thinking of things like “WALDO, TAKE THE ROUGH DEVICE, THE SMOOTH DEVICE, AND THE BUBBLY DEVICE” (in which it’s the first comma that we need to look at).

Probably the best way would be to read from the beginning of the command until the parser hits a verb (something like “[things], [topic]” in an Understand line would be ideal, but you can’t Understand commas). I don’t know how commands to other actors are parsed right now, but it might be possible to add this in there.

Yeah, commands to other actors are parsed depending on whether the first word is a verb (or direction?), so if you have a way of detecting “, verb” that should work. (All I know about the parser I learned from Ron Newcomb’s I7 implementation of the 6G60 parser, but that’s how I remember it there.) Seems like it might be harder or more I6y to parse, though.

Though my idea behind my third option was that we could just tell the player to type “waldo, take the rough device and the smooth device and the bubbly device.”

I’m looking through Parser.i6t right now, and it seems that it might be possible to add something like this:

  • If the parser would say “You seem to want to talk to someone…”:
  • Go back to the beginning of the command
  • Invoke the multi-noun parser routines on the first few words
  • If it gets somewhere before choking:
  • Change the token where it failed from a comma to a nonsense dictionary word (like $).

Then make I7 understand [things]$ [topic] as commanding it to. I’m really afraid to mess with the parser, but it shouldn’t be a huge change.

So far this seems to be working, although I spent a long time trying to figure out how ParseToken could return 2 (the list of return codes is wrong–2 means success, not 0). The biggest problem is the comma after the list of actors interfering with the list parser.

YES! It’s almost working! There are several bugs, but for the most part this is now what the parser does:

  • Is the beginning of the command a multiple object list?
  • If so, call the “multiple actor rulebook”, which copies the list somewhere else.
  • Set the actor to the player, clear the multiple object list, and continue parsing.

This works for commands in the form “IRIS, SENSA, WALDO, GO TO ALPHA FC” and “IRIS AND SENSA AND WALDO, GO TO ALPHA FC”, but not “IRIS, SENSA, AND WALDO, GO TO ALPHA FC”. I’m still trying to figure out why.
EDIT: Specifically, it breaks on “, AND” anywhere in the list. I don’t know why, but an “after reading a command” rule should take care of it.
EDIT2: It’s working pretty well now. I just need to plug in the code to convert the action. Here’s the relevant part (note that the I6 prints some debugging info to the screen, I’ll remove that later).

The multiple actor rules are a rulebook.
A multiple actor rule:
	say "Multiple actors: [line break]";
	repeat with the subject running through the multiple object list:
		say "    [the subject][line break]".
A last multiple actor rule:
	alter the multiple object list to {}.

Include (-
	! Only check for a comma (a "someone, do something" command) if we are
	! not already in the middle of one.  (This simplification stops us from
	! worrying about "robot, wizard, you are an idiot", telling the robot to
	! tell the wizard that she is an idiot.)
	
	if (actor == player) {
		for (j=2 : j<=num_words : j++) {
			i=NextWord();
			if (i == comma_word) jump Conversation;
		}
	}
	jump NotConversation;
	
	! NextWord nudges the word number wn on by one each time, so we've now
	! advanced past a comma.  (A comma is a word all on its own in the table.)
	
	.Conversation;
	
	j = wn - 1;
	
	! Use NounDomain (in the context of "animate creature") to see if the
	! words make sense as the name of someone held or nearby
	
	wn = 1; lookahead = HELD_TOKEN;
	scope_reason = TALKING_REASON;
! Here's where I'm changing things.
	l = ParseToken(ELEMENTARY_TT, MULTI_TOKEN);
	if(l == GPR_MULTIPLE){ ! Wtf -- the list of return codes for ParseToken is wrong! 2 is the code for success; 0 is the same as GPR_PREPOSITION.
		print "MULTIPLE ACTORS FOUND!!!^";
	}else if(l == GPR_REPARSE){
		print "Actor needs reparsing^";
	}else if(l == GPR_PREPOSITION){
		print "Preposition rejected^";
	}else if(l == -1){
		print "Error parsing actor^";
	}else{
		print "Actor found: ", (the) l, "^";
	}
	FollowRulebook((+the multiple actor rules+));
	wn--;
	print "WN is ", wn, "^";
	print "Verb_wordnum is ", verb_wordnum, "^";
	verb_wordnum = wn;
	jump BeginCommand;
! I cut the rest, since it's now redundant.
-) instead of "Parser Letter C" in "Parser.i6t".

Put code to deal with the actors in the “multiple actor rules”, replacing the simple list-printing rule.

The “, AND” problem was around for normal parsing at least in 6G60, so that’s probably just a limitation of the parser. As Sam said in that thread, you might just want to use a regex to zap the serial comma. (EDIT: I mean, zarf provides an I6 fix for it in that thread. Not sure if it works as-is in 6L02.) (On a quick but eccentric test it seems like it’s still there in 6L02 – “take apple, apple, and apple” gives a “you can’t see any such thing,” “take apple, apple and apple” takes one apple.)

Okay, I think I have a working system for multiple actors now. It completely replaces the standard “actor, action” parsing, so I’m going to do more rigorous testing before releasing it, but it seems to perform the way I want it to.

I’m surprised that parser problem has persisted for so long; since I’m rewriting bits of the I6 parser as it is, it shouldn’t be too hard to fix it there (if the previous token was “,” and the current token is “and” and we’re parsing for a MULTI_TOKEN, pretend we’ve parsed it and skip to the next word).

And it’s finished!

Include this extension to completely replace the standard I6 actor parsing. It requires a couple other extensions that are also in GitHub (although Debugging isn’t necessary at this point; comment out the “debug say” statements if you don’t want to use it), and one that isn’t: Serial And Fix by Andrew Plotkin is Zarf’s fix for the “x, y, and z” issue that he posted in the other thread.

This supports all of the following syntaxes:

JUMP
IRIS, JUMP
BOTH IRIS AND WALDO, JUMP
IRIS, WALDO, JUMP
IRIS AND WALDO, JUMP
IRIS, SENSA, AND WALDO, JUMP
IRIS AND SENSA AND WALDO, JUMP
ALL ROBOTS, JUMP
TELL IRIS TO JUMP
TELL IRIS AND WALDO TO JUMP
TELL ALL ROBOTS TO JUMP

(along with many variations on these.)

This also fixes the “leaflet problem”: if Iris and Waldo are in a room with two leaflets, “BOTH IRIS AND WALDO, TAKE LEAFLET” will make them each take a different one. TELL IRIS TO TELL WALDO TO JUMP doesn’t work, but now produces a reasonable error message* rather than going into an infinite loop. The same goes for other actions out of world.

Note: Certain parser error messages don’t look quite right, since the “player” variable is changed several times during parsing. I’m working on fixing this now, but it’s quite minor and doesn’t affect functionality.

Multiple Actors.i7x

Wow, I go away for a few days and come back to find a custom made extension. That’s amazing! Thank you!