A few questions about I7.

Hello,

New to I7 but I have a fair bit of coding experience. I have some questions.

Firstly, rooms. Can they be more than “one” area? So say for instance if I want a hallway, I want to have 8 exits for rooms as opposed to the generic NSEW options I have (obviously discounting up and down). Is there a way to make a room go “north” and open up extra rooms, or do I have to declare the various parts of the hallways as their own rooms?

Secondly, is there a way to “trade” two items between two characters but have it retain memory of who the original holder was. So say if A and B were trading items, B would have an item that I can put a variable on that says “used to belong to A”?

The easiest way to do this probably is to just make the hallway two rooms. Especially if you want to make the doors accessible by having the player go north–then you’re basically having them do exactly what they’d do if they were going into another room, so you might as well make it two rooms.

(You actually do have eight generic NSEW options–you can do diagonal directions like northeast. But often it’s just better design to restrict the number of exits in one room anyway, to avoid clutter.)

Sure, you can do something like this:

A thing has a person called the original owner.

Though that will give every thing an original owner (the default value will be the player, I think–that’s always the default value when you give something a person-based property, because it’s the one person who’s bound to exist).

You can also use a relation:

Original ownership relates one person (called the original owner) to various things. The verb to own means the original ownership relation.

This won’t give everything an original owner, and allows for some more flexible syntax than the property (which basically only allows syntax like “the original owner of the item described”).

For example:

[code]Piccadilly Circus is a room. Moriarty and Raffles are men in Piccadilly Circus.

A thing can be stealable.

Moriarty carries a cane and a watch. The cane and watch are stealable. Raffles carries a hat and a book. The hat and book are stealable.

Every turn:
let target be a random stealable thing;
if Raffles carries the target:
say “Moriarty abstracts [the target] from Raffles.”;
now Moriarty carries the target;
otherwise if Moriarty carries the target:
say “Raffles snaffles [the target] from Moriarty.”;
now Raffles carries the target.

Original ownership relates one person (called the original owner) to various things. The verb to own means the original ownership relation.

When play begins:
repeat with item running through stealable things:
if the item is carried by a person (called possessor):
now the possessor owns the item.

After jumping:
say “Startled, Moriarty and Raffles look up and notice you.”;
repeat with item running through stealable things:
if the item is carried by a person (called the thief) who does not own the item:
say “Shamefacedly, [the thief] returns [the item] to [the original owner of the item].”;
now the original owner of the item carries the item.[/code]

Notice that we can say both “now the possessor owns the item” and refer to “the original owner of the item.” Also, “item” and “target” are local variables without any special meaning, and the “When play begins” rule automatically sets the ownership relation at the beginning–we could set that by hand by saying things like “Raffles owns the hat” but that would be more typing and could be prone to errors if we forgot to set the declaration for something.

Looks like matt w beat me to the punch, but here are my thoughts.

Regarding rooms: you do have 8 exits besides up and down available: north, south, east, west, northeast, northwest, southeast, and southwest. If you need more than these, it would probably be best to split the hallway into distinct IF rooms representing different parts of the hallway. I haven’t explored the details, but, if you really want to have multiple rooms to the north of the hallway, then, when the player goes north, you could present them with a disambiguation menu asking which of the rooms to the north they want to enter. This would likely become irritating for players, so, from a design standpoint, I’d advise against it unless you also allow them to use alternate syntax like “enter dining room” to avoid the menu.

Regarding objects retaining a memory of past owners: you can store that info in a property and either set it at compilation time or when the game begins (if you want the original owner) or update it whenever the item exchanges hands (if you want the most recent previous owner). Here’s an example of the latter approach that uses some code from Example 207: Barter Barter:

Placeholder is a person.

Things have a person called the previous owner. The previous owner is usually Placeholder.

Instead of examining someone:
	say "[The noun] is carrying [the list of things carried by the noun]."

After examining something when the previous owner of the noun is not Placeholder:
	say "The previous owner of [the noun] is [the previous owner of the noun]."
		
Trading Post is a room.

Alice is a woman in the Trading Post. Alice carries an apple.

Bob is a man in the Trading Post.

Persuasion rule for asking people to try giving:
	persuasion succeeds.
	
The block giving rule is not listed in the check giving it to rules.

After an actor giving something to someone:
	now the previous owner of the noun is the actor;
	say "[The actor] gives [the noun] to [the second noun]."
	
Test me with "x alice / x apple / alice, give apple to bob / x apple / bob, give apple to alice / x apple"

There are also “in” and “out” directions (I forget whether those are their exact names), but it would be awfully confusing to use them in this context.

The proper names are “inside” and “outside”. They’re a bit more complicated to use than the other directions and yeah, I would not recommend them in a case like this…

Thanks for that, really helps.

I really don’t mind making another room for an extended length hallway, I really just didn’t want to copy+paste the same content twice and have two rooms named “upper Hallyway 1” and “upper hallway 2” and whatnot just for a few extra exits, but It looks like I have to.

Additionally, I’ve got another query. Is there some sort of way to randomly generate NPC characters? Say if I give a table of random stats, like an enemy, that may be a simple template with a few variations. Can I do that? Or do all npcs have to be statically created before runtime?

I had the thought of building a table with some fixed stats and randomly applying them to an NPC when a certain even occurs, so you could have random passers by that you may be able to steal things off, but wasn’t quite sure how to pull that off in I7. (In C# i’d just use a random number generator and a series of reasonably random ranges to choose from for stats li strength, height, stealth, etc…)

The simplest thing is probably to statically create a pool of blank slate NPC objects and keep them out of play. When you want to create a new NPC, move one out of the pool to the desired location and modify its properties to reflect the stats that it should have. When it dies or otherwise leaves play, move it back to the pool.

If you’re targeting the glulx vm, you could also use this dynamic objects extension to clone objects based on a prototype object.

In terms of the table of stats, you could have a bunch of prerolled characters in there to impose on your blank NPCs, with each row being the exact stats for that character. Alternatively, you could go meta and have a table of character types, where each row contains stat ranges for that type of character. In the former case, you’d just copy over the stats. In the latter case, you’d read the ranges from the table, generate random numbers within those ranges at runtime, and then apply the results to the NPC.

Here’s a rudimentary implementation of the simplest of the schemes that I described in my earlier post:

An NPC is a kind of person. An NPC is proper-named.
Understand the name property as describing an NPC.

An NPC has a text called name.

An NPC has numbers called strength, dexterity, intelligence, max health, and health.

Tavern is a room. "Type 'spawn' to create an NPC and 'despawn <NPC name>' to despawn an NPC. Examine an NPC to see its stats."

NPC Pool is a room. 

There are 3 NPCs in NPC Pool.

Instead of examining an NPC:
	say "[name of noun]: Str [strength of noun] Dex [dexterity of noun] Int [intelligence of noun] Health [health of noun]/[max health of noun][line break]".

To create an NPC:
	let N be a random NPC in NPC Pool;
	if N is nothing or num-NPCs-in-use is the number of rows in the Table of Pregenerated NPCs:
		say "No more NPCs are available right now.";
		stop;
	choose a random row in the Table of Pregenerated NPCs;
	while In Use entry is true:
		choose a random row in the Table of Pregenerated NPCs;
	now the In Use entry is true;
	now num-NPCs-in-use is num-NPCs-in-use + 1;
	now the name of N is the Name entry;
	now the strength of N is the Str entry;
	now the dexterity of N is the Dex entry;
	now the intelligence of N is the Int entry;
	now the max health of N is the Max Health entry;
	now the health of N is the max health of N;
	now the printed name of N is the name of N;
	now N is in location;
	say "Poof! [The name of N] appears!"

To destroy (N - NPC):
	now N is in NPC Pool;
	choose row with Name of the name of N in the Table of Pregenerated NPCs;
	now the In Use entry is false;
	now num-NPCs-in-use is num-NPCs-in-use - 1;
	say "Poof! [The name of N] vanishes!"

num-NPCs-in-use is a number that varies.

Table of Pregenerated NPCs
Name	Str	Dex	Int	Max Health	In Use
"Grunthos"	16	10	06 	32	false
"Barfhad"	17	12	08	27	false
"Bladez"	13	18	14	16	false
"Ninjabber"	14	16	09	19	false
"Bozz"	07	13	15	14	false
"Wizzy"	11	10	17	11	false

Spawning is an action applying to nothing.
Understand "spawn" as spawning.

Carry out spawning:
	Create an NPC.

Despawning is an action applying to one visible thing.
Understand "despawn [someone]" as despawning.

Check despawning:
	if the noun is not an NPC, instead say "You can't despawn [the noun]."

Carry out despawning:
	Destroy the noun.

Wow, that’s amazing, pretty much what I was after.

Final question, is there a way to group things together? So say if I have left arm and right arm I can collectively refer to them as arms, but still be able to handle them individually like left arm and right arm?

Yes. All that’s required is for the items to be referred to by a common word, and the parser should do the right thing. If not, you can help the parser along via explicit Understand statements.

There are other notions of grouping when the objects are identical (see §4.14 of the docs) or are part of a collection of things (e.g., The Seven Magical Teaspoons of Grendar) to be grouped in room descriptions or inventory listings (see §18.14), but they don’t apply to your example of arms, which is also slightly complicated because they’re body parts that are attached to the player.

Here’s an example where the player can draw on their arms, referring to them as arm, left arm, right arm, or arms:

A drawable object is a kind of thing. A drawable object can be marked or unmarked. A drawable object is usually unmarked.
A drawing implement is a kind of thing.

Instead of examining a drawable object, say "[The noun] is [if noun is marked]covered in ink[else]unmarked[end if]."

An arm is a kind of drawable object. 
A left arm is a kind of arm. A right arm is a kind of arm. 
A left arm is part of every person. A right arm is part of every person.
 
Drawing Room is a room.

A drawing implement called a magic marker is here.

Drawing on is an action applying to one visible thing.
Understand "draw on [something]" or "draw on [things]" as drawing on.

Check drawing on:
	if the player does not carry a drawing implement, instead say "You don't have anything with which to draw.";
	if the noun is not a drawable object, instead say "You can't draw on that.";
	if the noun is marked, instead say "You've already drawn on [the noun]."

Carry out drawing on:
	now the noun is marked.
	
Report drawing on:
	say "You scribble on [the noun] for a bit."
	
Instead of drawing on a drawing implement:
	say "Who do you think you are, Escher?"

Does the player mean drawing on drawing implement:
	it is unlikely.
	
Test me with "x arm / left / x right arm / draw on left arm / take marker / draw on arm / right / x left arm / x right arm / draw on arms"

In that example, we get the behavior we want because “left arm” and “right arm” both have the word “arm” in their names. Here’s another example (you can drop it into the code above, replacing the bit about arms) that gives the arms goofy names that don’t share a common word and instead relies on explicit Understand statements to get the grouping behavior:

An arm is a kind of drawable object. Understand "arm" as an arm. Understand "arms" as the plural of an arm.
A left dsfsfs is a kind of arm. A right djsldjsl is a kind of arm. 
A left dsfsfs is part of every person. A right djsldjsl is part of every person.
The printed name of a left dsfsfs is "[regarding the holder of the item described][possessive] left arm".
The printed name of a right djsldjsl is "[regarding the holder of the item described][possessive] right arm".

As you can see, the Understand statements control how the player describes the objects to the game, while the printed name statements control how the game describes the objects to the player. The internal names of the objects (containing gibberish in this example) don’t necessarily have to be related to either, although you should pick meaningful internal names because the source code will need to use them.

You can also add “Bob is a person in the Drawing Room.” for more arm-drawing hijinks like “draw on left arms”.

Cool, thanks again for that.

so if I wanted to have an amputee character for instance, so that was missing an arm but I still wanted to be able to call “arms” and have the parser figure it out would I just put .

a left arm is a kind of arm. a person usually has a left arm.

And then just in the code create the person and declare they don’t have a left arm? Would saying “arms” still work in the rest of the context or would I always need an if statement delineating between an amputee character and an able one?

"Arms and that Man"

An arm is a kind of thing.  
A right arm is a kind of arm.  Understand "arm" as a right arm.
A left arm is a kind of arm.  Understand "arm" as a left arm.
One right arm is part of every man.  One right arm is part of every woman.
One left arm is part of every man.  One left arm is part of every woman.
My right arm is an arm.  It is part of the player.  The printed name of my right arm is "your right arm".
My left arm is an arm.  It is part of the player.  The printed name of my left arm is "your left arm".  


The Touchless Car Wash is a room. 

Stubby is a man in Touchless Car Wash.

[Now the trick is to *not* detach an actual missing arm, or the player cannot refer to it.]

The description of Stubby's right arm is "Stubby's right arm is actually missing from when he was in the war, but he doesn't like to talk about it."  

Understand "nub/stub" and "nub/stub of flesh" as Stubby's right arm.  Understand "stubb/Stubby's" as Stubby.

Instead of doing anything except examining to Stubby's right arm, say "That's existentially impossible."

The only problem is saying “x my arms” doesn’t work. I can’t think of that workaround, but someone should be able to.

(I clearly have too much time on my hands. TL;DR at the end of the post.)

I’ve taken a look at the I6 intermediate code and the parser trace output in an effort to find the issue.

I found an old discussion of a similar issue (arms and “my”) on r.a.i-f that didn’t seem to go anywhere. This one seems not to be directly related, but it also discusses some weirdness with “my”.

Looking at the I6 code, I noticed some differences between the player’s arms and Stubby’s arms, but most of them turned out to be because the player’s arms are declared as “an arm” instead of “a left arm” and “a right arm.” Fixing this made the resulting I6 objects more uniform, but didn’t resolve the parsing problem. The other difference between the arms is that Stubby’s right arm has its own parse_name routine, but that makes sense because it has an understand statement written about it to recognize “nub/stub” and such.

This is the behavior that I see when running HanonO’s code:

The problem seems to arise only when the word “my” is used to refer to the arms. Here’s the brief parser trace output for the last two commands:

Notice that in the latter case the parser fails to recognize “take my arms” as matching grammar line 1 (“take [things]”) and instead continues trying to match lines until it seems to find an erroneous match with line 5 (“take [things] off [thing]”) despite the word ‘off’ never appearing in the input.

Here’s the more verbose level 5 trace output (in rant tags because it’s extremely long):
[rant=Verbose Parser Trace"][code]

trace 5
[Parser tracing set to level 5.]

take stubby’s arms
[ “take” take / “stubby’s” stubby’s / “arms” arms ]
[Parsing for the verb ‘take’ (6 lines)]

[line 0 * ‘inventory’ → Inv]
[line rejected for not ending with correct preposition]

[line 1 * multi → Take]
[line 1 token 1 word 2 : multi]
[Object list from word 2]
[Calling NounDomain on location and actor]
[NounDomain called at word 2
seeking definite object
Trying the north (579291) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the northeast (579323) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the northwest (579355) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the south (579387) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the southeast (579419) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the southwest (579451) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the east (579483) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the west (579515) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the up (579547) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the down (579579) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the inside (579611) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying the outside (579643) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying yourself (579675) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying your right arm (579707) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying your left arm (579739) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Trying Stubby (579803) at word 2
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby’s right arm (579835) at word 2
Parse_name called
Pass 1: 2 Pass 2: 2 Pass 3: 2
Matched (2)
Trying Stubby’s left arm (579867) at word 2
Parse_name called
Pass 1: 2 Pass 2: 2 Pass 3: 2
Matched (2)
[ND made 2 matches]
[Adjudicating match list of size 2 in context 2
indefinite type: plural
number wanted: all
most likely GNAs of names: 4095
Scoring match list: indef mode 1 type 8, satisfying 0 requirements:

 Stubby's right arm (579835) in nothing : 2136 points

 Stubby's left arm (579867) in nothing : 2136 points

Best guess Stubby’s right arm (579835)
Accepting it
Best guess Stubby’s left arm (579867)
Accepting it
Best guess ran out of choices
Made multiple object of size 2]
[ND appended to the multiple object list:
Entry 1: Stubby’s right arm (579835)
Entry 2: Stubby’s left arm (579867)
List now has size 2]
[token resulted in success]
[line 1 token 2 word 4 : END]
Revising multiple object list of size 2 with 2nd nothing
Token 2 plural case: number with actor 0
Done: new size 2
[Line successfully parsed]

Stubby’s right arm: That’s existentially impossible.
Stubby’s left arm: That seems to be a part of Stubby.

take my arms
[ “take” take / “my” my / “arms” arms ]
[Parsing for the verb ‘take’ (6 lines)]

[line 0 * ‘inventory’ → Inv]
[line rejected for not ending with correct preposition]

[line 1 * multi → Take]
[line 1 token 1 word 2 : multi]
[Object list from word 2]
[Calling NounDomain on location and actor]
[NounDomain called at word 3
seeking indefinite object: my
number wanted: 0
most likely GNAs of names: 4095
Trying yourself (579675) at word 3
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Matched (0)
Trying your right arm (579707) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying your left arm (579739) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby (579803) at word 3
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Matched (0)
Trying Stubby’s right arm (579835) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby’s left arm (579867) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
[ND made 4 matches]
[Adjudicating match list of size 4 in context 2
indefinite type: my plural
number wanted: all
most likely GNAs of names: 4095
Scoring match list: indef mode 1 type 10, satisfying 1 requirements:
your right arm (579707) in nothing is rejected (doesn’t match descriptors)
your left arm (579739) in nothing is rejected (doesn’t match descriptors)
Stubby’s right arm (579835) in nothing is rejected (doesn’t match descriptors)
Stubby’s left arm (579867) in nothing is rejected (doesn’t match descriptors)
[ND appended to the multiple object list:
List now has size 0]
[token resulted in success]
[line 1 token 2 word 4 : END]
Revising multiple object list of size 0 with 2nd nothing
Token 2 plural case: number with actor 0
Done: new size 0

[line 2 * ‘off’ noun → Disrobe]
[line 2 token 1 word 2 : ‘off’]
[token resulted in failure with error type 1]

[line 3 * noun ‘off’ → Disrobe]
[line rejected for not ending with correct preposition]

[line 4 * multiinside ‘from’ noun → Remove]
[Trying look-ahead]
[Look-ahead aborted: prepositions missing]
[line 4 token 1 word 2 : multiinside]
[Object list from word 2]
[Calling NounDomain on location and actor]
[NounDomain called at word 3
seeking indefinite object: my
number wanted: 0
most likely GNAs of names: 4095
Trying yourself (579675) at word 3
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Matched (0)
Trying your right arm (579707) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying your left arm (579739) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby (579803) at word 3
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Matched (0)
Trying Stubby’s right arm (579835) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby’s left arm (579867) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
[ND made 4 matches]
[Adjudicating match list of size 4 in context 5
indefinite type: my plural
number wanted: all
most likely GNAs of names: 4095
Scoring match list: indef mode 1 type 10, satisfying 1 requirements:
your right arm (579707) in nothing is rejected (doesn’t match descriptors)
your left arm (579739) in nothing is rejected (doesn’t match descriptors)
Stubby’s right arm (579835) in nothing is rejected (doesn’t match descriptors)
Stubby’s left arm (579867) in nothing is rejected (doesn’t match descriptors)
[ND appended to the multiple object list:
List now has size 0]
[token resulted in success]
[line 4 token 2 word 4 : ‘from’]
[token resulted in success]
[line 4 token 3 word 4 : noun]
[Object list from word 4]
[Calling NounDomain on location and actor]
[NounDomain called at word 4
seeking definite object
Trying the north (579291) at word 4
Matched (0)
Trying the northeast (579323) at word 4
Matched (0)
Trying the northwest (579355) at word 4
Matched (0)
Trying the south (579387) at word 4
Matched (0)
Trying the southeast (579419) at word 4
Matched (0)
Trying the southwest (579451) at word 4
Matched (0)
Trying the east (579483) at word 4
Matched (0)
Trying the west (579515) at word 4
Matched (0)
Trying the up (579547) at word 4
Matched (0)
Trying the down (579579) at word 4
Matched (0)
Trying the inside (579611) at word 4
Matched (0)
Trying the outside (579643) at word 4
Matched (0)
Trying yourself (579675) at word 4
Matched (0)
Trying your right arm (579707) at word 4
Matched (0)
Trying your left arm (579739) at word 4
Matched (0)
Trying Stubby (579803) at word 4
Matched (0)
Trying Stubby’s right arm (579835) at word 4
Matched (0)
Trying Stubby’s left arm (579867) at word 4
Matched (0)
[ND made 18 matches]
[Adjudicating match list of size 18 in context 0
definite object
Scoring match list: indef mode 0 type 0, satisfying 0 requirements:

 The north (579291) in the compass : 2106 points

 The northeast (579323) in the compass : 2106 points

 The northwest (579355) in the compass : 2106 points

 The south (579387) in the compass : 2106 points

 The southeast (579419) in the compass : 2106 points

 The southwest (579451) in the compass : 2106 points

 The east (579483) in the compass : 2106 points

 The west (579515) in the compass : 2106 points

 The up (579547) in the compass : 2106 points

 The down (579579) in the compass : 2106 points

 The inside (579611) in the compass : 2106 points

 The outside (579643) in the compass : 2106 points

 You (579675) in the Touchless Car Wash : 2071 points

 your right arm (579707) in nothing : 2136 points

 your left arm (579739) in nothing : 2136 points

 Stubby (579803) in the Touchless Car Wash : 2176 points

 Stubby's right arm (579835) in nothing : 2136 points

 Stubby's left arm (579867) in nothing : 2136 points

Single best-scoring object returned.]
[ND returned Stubby]
[token resulted in success]
[line 4 token 4 word 4 : END]
Revising multiple object list of size 0 with 2nd Stubby
Done: new size 0

[line 5 * multiinside ‘off’ noun → Remove]
[Trying look-ahead]
[Look-ahead aborted: prepositions missing]
[line 5 token 1 word 2 : multiinside]
[Object list from word 2]
[Calling NounDomain on location and actor]
[NounDomain called at word 3
seeking indefinite object: my
number wanted: 0
most likely GNAs of names: 4095
Trying yourself (579675) at word 3
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Matched (0)
Trying your right arm (579707) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying your left arm (579739) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby (579803) at word 3
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
Matched (0)
Trying Stubby’s right arm (579835) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
Trying Stubby’s left arm (579867) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
Matched (1)
[ND made 4 matches]
[Adjudicating match list of size 4 in context 5
indefinite type: my plural
number wanted: all
most likely GNAs of names: 4095
Scoring match list: indef mode 1 type 10, satisfying 1 requirements:
your right arm (579707) in nothing is rejected (doesn’t match descriptors)
your left arm (579739) in nothing is rejected (doesn’t match descriptors)
Stubby’s right arm (579835) in nothing is rejected (doesn’t match descriptors)
Stubby’s left arm (579867) in nothing is rejected (doesn’t match descriptors)
[ND appended to the multiple object list:
List now has size 0]
[token resulted in success]
[line 5 token 2 word 4 : ‘off’]
[token resulted in success]
[line 5 token 3 word 4 : noun]
[Object list from word 4]
[Calling NounDomain on location and actor]
[NounDomain called at word 4
seeking definite object
Trying the north (579291) at word 4
Matched (0)
Trying the northeast (579323) at word 4
Matched (0)
Trying the northwest (579355) at word 4
Matched (0)
Trying the south (579387) at word 4
Matched (0)
Trying the southeast (579419) at word 4
Matched (0)
Trying the southwest (579451) at word 4
Matched (0)
Trying the east (579483) at word 4
Matched (0)
Trying the west (579515) at word 4
Matched (0)
Trying the up (579547) at word 4
Matched (0)
Trying the down (579579) at word 4
Matched (0)
Trying the inside (579611) at word 4
Matched (0)
Trying the outside (579643) at word 4
Matched (0)
Trying yourself (579675) at word 4
Matched (0)
Trying your right arm (579707) at word 4
Matched (0)
Trying your left arm (579739) at word 4
Matched (0)
Trying Stubby (579803) at word 4
Matched (0)
Trying Stubby’s right arm (579835) at word 4
Matched (0)
Trying Stubby’s left arm (579867) at word 4
Matched (0)
[ND made 18 matches]
[Adjudicating match list of size 18 in context 0
definite object
Scoring match list: indef mode 0 type 0, satisfying 0 requirements:

 The north (579291) in the compass : 2106 points

 The northeast (579323) in the compass : 2106 points

 The northwest (579355) in the compass : 2106 points

 The south (579387) in the compass : 2106 points

 The southeast (579419) in the compass : 2106 points

 The southwest (579451) in the compass : 2106 points

 The east (579483) in the compass : 2106 points

 The west (579515) in the compass : 2106 points

 The up (579547) in the compass : 2106 points

 The down (579579) in the compass : 2106 points

 The inside (579611) in the compass : 2106 points

 The outside (579643) in the compass : 2106 points

 You (579675) in the Touchless Car Wash : 2071 points

 your right arm (579707) in nothing : 2136 points

 your left arm (579739) in nothing : 2136 points

 Stubby (579803) in the Touchless Car Wash : 2176 points

 Stubby's right arm (579835) in nothing : 2136 points

 Stubby's left arm (579867) in nothing : 2136 points

Single best-scoring object returned.]
[ND returned Stubby]
[token resulted in success]
[line 5 token 4 word 4 : END]
Revising multiple object list of size 0 with 2nd Stubby
Done: new size 0
He seems to belong to Stubby.

[/code][/rant]
There are three interesting things to look at here. (1) why “take stubby’s arms” correctly matches grammar line 1, (2) why “take my arms” doesn’t correctly match grammar line 1, and (3) why “take my arms” incorrectly matches grammar line 5.

Putting aside (3) for a moment, let’s compare (1) and (2):

   seeking definite object
[...]
    Trying your right arm (579707) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
    Trying your left arm (579739) at word 2
Parse_name called
Pass 1: 0 Pass 2: 0 Pass 3: 0
[...]
    Trying Stubby's right arm (579835) at word 2
Parse_name called
Pass 1: 2 Pass 2: 2 Pass 3: 2
    Matched (2)
    Trying Stubby's left arm (579867) at word 2
Parse_name called
Pass 1: 2 Pass 2: 2 Pass 3: 2
    Matched (2)
   [ND made 2 matches]
   seeking indefinite object: my 
   number wanted: 0
[...]
    Trying your right arm (579707) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
    Matched (1)
    Trying your left arm (579739) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
    Matched (1)
[...]
    Trying Stubby's right arm (579835) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
    Matched (1)
    Trying Stubby's left arm (579867) at word 3
Parse_name called
Pass 1: 1 Pass 2: 1 Pass 3: 1
    Matched (1)
   [ND made 4 matches]

Notice that, in case (1), we start looking for the object at word 2, so we’re trying to match “stubby’s arms”, and we get strong matches for stubby’s right arm and left arm. In case (2), the parser consumes the “my” and special cases it in some way and then starts looking for the object at word 3, so we’re trying to match “arms”, and we get matches for all four arms. The I6 objects look like this (refer to the name properties now, the component stuff will come into play later in this post):

Object I127_my_right_arm ""
[...]
    with component_parent selfobj
    with component_sibling I128_my_left_arm
[...]
    with name 'my' 'right' 'arm'  'arms//p' 'arm'
[...]
;

Object I128_my_left_arm ""
[...]
    with component_parent selfobj
[...]
    with name 'my' 'left' 'arm'  'arms//p' 'arm' 
[...]
;

Object I131_stubby_s_right_arm ""
[...]
    with component_parent I130_stubby
    with component_sibling I132_stubby_s_left_arm
[...]
    with name 'stubby^s' 'right' 'arm'  'arms//p' 'arm' 
[...]
;

Object I132_stubby_s_left_arm ""
[...]
    with component_parent I130_stubby
[...]
    with name 'stubby^s' 'left' 'arm'  'arms//p' 'arm' 
[...]
;

The “my” in these name properties seems to come from the objects being declared as “My left arm” and “My right arm” in the source code, not because they were declared as parts of the player, and we don’t benefit from it at all since the parser is only trying to match “arms”. So we can only hope that the parser’s seeking “indefinite object: my” is going to take this component relationship into account in some way.

Once the lists of matching objects have been assembled (2 arms in the “stubby’s arms” case and all 4 arms in the “my arms” case), we have this:

   [Adjudicating match list of size 2 in context 2
   indefinite type: plural 
   number wanted: all
   most likely GNAs of names: 4095
   Scoring match list: indef mode 1 type 8, satisfying 0 requirements:

     Stubby's right arm (579835) in nothing : 2136 points

     Stubby's left arm (579867) in nothing : 2136 points
   Best guess Stubby's right arm (579835)
   Accepting it
   Best guess Stubby's left arm (579867)
   Accepting it
   Best guess ran out of choices
   Made multiple object of size 2]
  [ND appended to the multiple object list:
  Entry 1: Stubby's right arm (579835)
  Entry 2: Stubby's left arm (579867)
  List now has size 2]
  [token resulted in success]
 [line 1 token 2 word 4 : END]
   Revising multiple object list of size 2 with 2nd nothing
   Token 2 plural case: number with actor 0
   Done: new size 2
[Line successfully parsed]
   [Adjudicating match list of size 4 in context 2
   indefinite type: my plural 
   number wanted: all
   most likely GNAs of names: 4095
   Scoring match list: indef mode 1 type 10, satisfying 1 requirements:
   your right arm (579707) in nothing is rejected (doesn't match descriptors)
   your left arm (579739) in nothing is rejected (doesn't match descriptors)
   Stubby's right arm (579835) in nothing is rejected (doesn't match descriptors)
   Stubby's left arm (579867) in nothing is rejected (doesn't match descriptors)
  [ND appended to the multiple object list:
  List now has size 0]
  [token resulted in success]
 [line 1 token 2 word 4 : END]
   Revising multiple object list of size 0 with 2nd nothing
   Token 2 plural case: number with actor 0
   Done: new size 0

[line 2 * 'off' noun -> Disrobe]
[...]

We see that in case (1), the scoring process gives each of stubby’s arms 2136 points and accepts them both, while, in case (2), the scoring process rejects all four arms for the vague reason “doesn’t match descriptors”. We also see that, in case (2), the “my” seems to be in play still since we have “indefinite type: my plural” and “satisfying 1 requirements” vs. “indefinite type: plural” and “satisfying 0 requirements”.

At this point, we want to go look at the scoring function in the parser, and we find it in the Inform 6 intermediate code – ScoreMatchL:
[rant=ScoreMatchL] Constant OTHER_BIT = 1; ! These will be used in Adjudicate() Constant MY_BIT = 2; ! to disambiguate choices Constant THAT_BIT = 4; Constant PLURAL_BIT = 8; Constant LIT_BIT = 16; Constant UNLIT_BIT = 32; [code]
[ ScoreMatchL context its_owner its_score obj i j threshold met a_s l_s;
! if (indef_type & OTHER_BIT ~= 0) threshold++;
if (indef_type & MY_BIT ~= 0) threshold++;
if (indef_type & THAT_BIT ~= 0) threshold++;
if (indef_type & LIT_BIT ~= 0) threshold++;
if (indef_type & UNLIT_BIT ~= 0) threshold++;
if (indef_owner ~= nothing) threshold++;

#Ifdef DEBUG;
if (parser_trace >= 4) print "   Scoring match list: indef mode ", indef_mode, " type ",
  indef_type, ", satisfying ", threshold, " requirements:^";
#Endif; ! DEBUG

#ifdef PREFER_HELD;
a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
if (action_to_be == ##Take or ##Remove) {
    a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
}
context = context;  ! silence warning
#ifnot;
a_s = SCORE__NEXTBESTLOC; l_s = SCORE__BESTLOC;
if (context == HELD_TOKEN or MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN) {
    a_s = SCORE__BESTLOC; l_s = SCORE__NEXTBESTLOC;
}
#endif; ! PREFER_HELD

for (i=0 : i<number_matched : i++) {
    obj = match_list-->i; its_owner = parent(obj); its_score=0; met=0;

    !      if (indef_type & OTHER_BIT ~= 0
    !          &&  obj ~= itobj or himobj or herobj) met++;
    if (indef_type & MY_BIT ~= 0 && its_owner == actor) met++;
    if (indef_type & THAT_BIT ~= 0 && its_owner == actors_location) met++;
    if (indef_type & LIT_BIT ~= 0 && obj has light) met++;
    if (indef_type & UNLIT_BIT ~= 0 && obj hasnt light) met++;
    if (indef_owner ~= 0 && its_owner == indef_owner) met++;

    if (met < threshold) {
        #Ifdef DEBUG;
        if (parser_trace >= 4)
        	print "   ", (The) match_list-->i, " (", match_list-->i, ") in ",
        	    (the) its_owner, " is rejected (doesn't match descriptors)^";
        #Endif; ! DEBUG
        match_list-->i = -1;
    }
    else {
        its_score = 0;
        if (obj hasnt concealed) its_score = SCORE__UNCONCEALED;

        if (its_owner == actor) its_score = its_score + a_s;
        else
            if (its_owner == actors_location) its_score = its_score + l_s;
            else
                if (its_owner ~= compass) its_score = its_score + SCORE__NOTCOMPASS;

        its_score = its_score + SCORE__CHOOSEOBJ * ChooseObjects(obj, 2);

        if (obj hasnt scenery) its_score = its_score + SCORE__NOTSCENERY;
        if (obj ~= actor) its_score = its_score + SCORE__NOTACTOR;

        !   A small bonus for having the correct GNA,
        !   for sorting out ambiguous articles and the like.

        if (indef_cases & (PowersOfTwo_TB-->(GetGNAOfObject(obj))))
            its_score = its_score + SCORE__GNA;

        match_scores-->i = match_scores-->i + its_score;
        #Ifdef DEBUG;
        if (parser_trace >= 4) print "     ", (The) match_list-->i, " (", match_list-->i,
          ") in ", (the) its_owner, " : ", match_scores-->i, " points^";
        #Endif; ! DEBUG
    }
 }

for (i=0 : i<number_matched : i++) {
    while (match_list-->i == -1) {
        if (i == number_matched-1) { number_matched--; break; }
        for (j=i : j<number_matched-1 : j++) {
            match_list-->j = match_list-->(j+1);
            match_scores-->j = match_scores-->(j+1);
        }
        number_matched--;
    }
}

];
[/code][/rant]
We can see from this code that the “doesn’t match descriptors” error happens when met < threshold and that “satisfying 1 requirements” indicates that the threshold is 1. We can see that indef_type is 10, which is consistent with PLURAL_BIT (8) and MY_BIT (2) being set. The crux of the issue seems to be this:

    if (indef_type & MY_BIT ~= 0)    threshold++;
[...]
    for (i=0 : i<number_matched : i++) {
        obj = match_list-->i; its_owner = parent(obj); its_score=0; met=0;

        !      if (indef_type & OTHER_BIT ~= 0
        !          &&  obj ~= itobj or himobj or herobj) met++;
        if (indef_type & MY_BIT ~= 0 && its_owner == actor) met++;
        if (indef_type & THAT_BIT ~= 0 && its_owner == actors_location) met++;
        if (indef_type & LIT_BIT ~= 0 && obj has light) met++;
        if (indef_type & UNLIT_BIT ~= 0 && obj hasnt light) met++;
        if (indef_owner ~= 0 && its_owner == indef_owner) met++;

        if (met < threshold) {
            #Ifdef DEBUG;
            if (parser_trace >= 4)
            	print "   ", (The) match_list-->i, " (", match_list-->i, ") in ",
            	    (the) its_owner, " is rejected (doesn't match descriptors)^";
            #Endif; ! DEBUG
            match_list-->i = -1;
        }
[...]
  }

its_owner, set to the parent of the object under consideration, needs to be == actor in order for met to be incremented so that it can meet the threshold bump that the presence of the MY_BIT created, but when we told Inform 7 that my left arm and my right arm are part of the player, that did not cause Inform 7 to make those arms children of the player object. Instead, it used component_parent and component_sibling properties (see the I6 object code from earlier in the post) to model this relationship. We can see that the arm objects have no parent using the TREE debug command:

>tree
[...]
your right arm (579707) 
your left arm (579739) 
Touchless Car Wash (579771) 
  yourself
  Stubby
Stubby's right arm (579835) 
Stubby's left arm (579867) 

So, it seems that, if we want “my” to refer to objects, those objects need to be children of the player. Inform 7 doesn’t allow objects to be both parts of the player and carried by the player. It also seems that Inform 7 ignores the concealed bit when an object is carried by the player. So, I’m not able to come up with a solid workaround. However, we can see that the scoring function behaves better if the player carries the arms instead of them being part of the player:

   [Adjudicating match list of size 4 in context 2
   indefinite type: my plural 
   number wanted: all
   most likely GNAs of names: 4095
   Scoring match list: indef mode 1 type 10, satisfying 1 requirements:
   Stubby's right arm (579835) in nothing is rejected (doesn't match descriptors)
   Stubby's left arm (579867) in nothing is rejected (doesn't match descriptors)

     your right arm (579707) in yourself : 1156 points

     your left arm (579739) in yourself : 1156 points
   Best guess your right arm (579707)
   Rejecting it
   Best guess your left arm (579739)
   Rejecting it
   Best guess ran out of choices
   Made multiple object of size 0]
  [ND appended to the multiple object list:
  List now has size 0]
  [token resulted in success]
 [line 1 token 2 word 4 : END]
   Revising multiple object list of size 0 with 2nd nothing
   Token 2 plural case: number with actor 0
   Done: new size 0

Here we see Stubby’s arms getting rejected because they’re not children of the player, while the player’s arms are accepted and scored with 1156 points each. However, they’re rejected as options later in the parsing process (within this chunk of the Adjudicate function):

        for (j=BestGuess(): j~=-1 && i<indef_wanted && i+offset<MATCH_LIST_WORDS-1:
        	j=BestGuess()) {
            flag = 0;
            BeginActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
            if ((ForActivity(DECIDING_WHETHER_ALL_INC_ACT, j)) == 0) {

                if (j hasnt concealed && j hasnt worn) flag = 1;
            
                if (context == MULTIHELD_TOKEN or MULTIEXCEPT_TOKEN && parent(j) ~= actor)
                    flag = 0;

                if (action_to_be == ##Take or ##Remove && parent(j) == actor)
                    flag = 0;

                k = ChooseObjects(j, flag);

                if (k == 1)
                    flag = 1;
                else {
                    if (k == 2) flag = 0;
                }
            } else {
                flag = 0; if (RulebookSucceeded()) flag = 1;
            }
            EndActivity(DECIDING_WHETHER_ALL_INC_ACT, j);
            if (flag == 1) {
                i++; multiple_object-->(i+offset) = j;
                #Ifdef DEBUG;
                if (parser_trace >= 4) print "   Accepting it^";
                #Endif; ! DEBUG
            }
            else {
                i = i;
                #Ifdef DEBUG;
                if (parser_trace >= 4) print "   Rejecting it^";
                #Endif; ! DEBUG
            }
        }

What going on here is that if flag is set to 1 at the end of this fragment, the object is accepted, otherwise it’s rejected. The problem seems to be the test for action_to_be == #TAKE and parent of j (the arm) being the actor. In other words, it’s rejected because we’re trying to take something that we already have. Note that, when the arms are carried by the player, “drop my arms” works just fine:

providing further evidence that it’s taking something that’s already owned that causes the problem in Adjudicate. It looks like ChooseObjects (or whatever influences it in I7) could override this, although we’d still be carrying around two visible severed arms. But at least we could say “take my arms” and be told that we already have them rather than “He seems to belong to Stubby.” I’d like to write something like:

[ ChooseObjects obj code;
  if (code == false && indef_type & MY_BIT ~= 0 && action_to_be == #Take && obj provides component_parent && obj.component_parent == selfobj) {
      return 1;
  }
];

but there’s already a ChooseObjects in place that I’d need to replace or augment.

So, yeah.

TL;DR: “my” doesn’t work in Inform 7 for things that are part of the player, because they aren’t children of the player object. I7 should also consider component_parent == selfobj when parsing “my”.

1 Like

zoom right over my head :slight_smile:

Anyway, that’s something I can work around for now.

New question, is there a way to make NPCs follow paths. I know I can move NPCs immediately to another location, that’s easy. But right now i’m trying to build a schedule so that NPCs will travel to certain locations during certain times of the day (for now just certain turn numbers). I want to have NPC followable, so say they’re lingering around a room for 2-3 turns before moving on. And the pathing will vary (think a character going to school, then later going from home to the mall, etc…).

I figure I need to store a table of paths (home to school, school to mall, mall to home) but is there any other option? Is there an extension somewhere that lets me have the NPC figure it out if I just give it room names or something? Having static paths removes some of the dynamics and keeps me from letting characters "Roam " in certain ares for certain periods.

Yeah, check out §7.13 of the Recipe Book (the manual with the yellow background) entitled Traveling Characters. There are 6 examples there of different kinds of moving characters (random, predefined path stored in a table, dynamic routing to find the next step towards a destination, and more).

vlaviano, I brought up your comprehensive analysis of the situation and put your TL;DR (which I only half-understood!) to Emily Short. Her reply: