From chat: Letting the player name things in I7

Thank you. I may have understood this “one visible thing” nonsense at some point and it slipped my mind. But not once have I ever had to care about the distinction between ‘thing’ and ‘object’!

1 Like

Things live in the world – they can be placed in a room, carried, contained, supported, etc. Objects have no location.

(This is not a rigid rule but it’s the simplest way to think about it.)

2 Likes

This is indeed the implementation I was imagining from the moment it was brought up in chat. I would imagine this would be fun to flesh out as the core mechanic of a game if I understood how to make it work. Thank you for articulating my thoughts better than I did.

2 Likes

This can be done without a lot of code…

(I cut corners and just made spell a kind of thing that’s either unnamed or named.)

5 Likes

I’m stealing both of these code examples (with credit to the authors) for the Handbook. Thanks, guys!

4 Likes

Note that ‘verbless grammar’ (i.e. 'Understand ... phrases that start with a [token in square brackets] rather than a verb word) is still not fully supported in Inform 10.1.2.

The bug in 9.3/6M62 that led to incorrect allocation of topic snippets after commands such as ‘Darcy, hello’ (see here) when a story included any verbless grammar has been fixed in 10.1.2, but it remains the case that if you have a word that is both a true recognised verb word and also a word that is recognised by your ‘verbless grammar’ token, the parser will try to parse it only as a true verb word and never as a match to the ‘verbless grammar’ token (see also here).

In this instance, if you name your spell as a word that is already a true recognised verb word, your spell will never fire because the parser gives up after having failed to match a correct grammar line to the recognised verb word without then going on to consider the possibility of matching to ‘verbless grammar’.

e.g.

Lab
 
>name spell answer
You name your spell Answer.
 
>answer
I didn't understand that sentence.
4 Likes

OK, so we’ll block existing command verbs and pseudo-verbs.

use DICT_WORD_SIZE of 15.

To say again-word: (- Glulx_PrintAnything(AGAIN1__WD); -).
To say again-alias: (- Glulx_PrintAnything(AGAIN2__WD); -).
To say oops-word: (- Glulx_PrintAnything(OOPS1__WD); -).
To say oops-alias: (- Glulx_PrintAnything(OOPS2__WD); -).
To say undo-word: (- Glulx_PrintAnything(UNDO1__WD); -).

Include (-
! 1-indexed for I7's convenience
[ printDictWord i;
print (address) #dictionary_table + WORDSIZE + ((i-1) * (DICT_WORD_SIZE + 7));
];
-)

To say dictionary entry (n - a number): (- printDictWord({n}); -)

To decide what number is the dictionary size: (- #dictionary_table-->0 -)

The dictionary list is a list of texts variable.

To build the dictionary list:
  if the dictionary list is empty begin;
    add the substituted form of "[again-word]" to dictionary list;
    add the substituted form of "[again-alias]" to dictionary list;
    add the substituted form of "[oops-word]" to dictionary list;
    add the substituted form of "[oops-alias]" to dictionary list;
    add the substituted form of "[undo-word]" to dictionary list;
    repeat with i running from 1 to the dictionary size begin;
      add the substituted form of "[dictionary entry i]" to dictionary list;
    end repeat;
  end if;

when play begins:
  build the dictionary list;

Last check naming something:
	if word number 1 in "[the topic understood]" is listed in the dictionary list, instead say "Try something a little more original and distinctive, wizard."

It’s that easy! In 6M62, at least. In 10.1, for your convenience, you can’t compile an inclusion using #dictionary_table and would have to write a kit.

4 Likes

Neat! I would have thought that you don’t need to build a copy of and exclude the whole dictionary- only those words flagged in the dictionary as being verb words?

1 Like

I suppose you don’t. But I’m still a bull in the parser’s china shop, so what’s a good way to get just the command verbs? (And don’t skimp on the directions and their abbreviations as pseudo-commands of interest!)

1 Like

Include (-
[ PrintCommandWords wd i j dictlen entrylen entry;
    dictlen = #dictionary_table-->0;
    entrylen = DICT_WORD_SIZE + 7;
    for (j=0 : j<dictlen: j++ ) {
        wd = #dictionary_table + WORDSIZE + entrylen*j; 
        entry = DictionaryWordToVerbNum(wd);
         if ((entry >= 0) && (entry < dictlen))
            print (address) wd, "^";
    }
];
-)



To say command words: (- PrintCommandWords(); -)

The dictionary list is a list of texts variable.

To decide what text is the expanded (T - a text) (this is expanded-text): decide on the substituted form of T.

To build the dictionary list:
  if the dictionary list is empty begin;
    now the dictionary list is { "[again-word]", "[again-alias]", "[oops-word]", "[oops-alias]", "[undo-word]" };
   repeat with dir running through the directions begin;
      add the substituted form of "[dir]" to dictionary list;
   end repeat;
   now dictionary list is expanded-text applied to dictionary list;
   let wordlist be the substituted form of "[command words]";
   repeat with i running from 1 to (the number of lines in wordlist) begin;
      add line number i in wordlist to dictionary list;
   end repeat;
   sort the dictionary list;
  end if;

lab is a room.

when play begins: build the dictionary list; say the dictionary list

Still need to get the direction abbreviations.

1 Like

A dictionary entry in Inform-generated code consists of the word itself, followed by three bytes of data. Bit 0 of byte 1 is set if the word can be used as a verb. I’m not sure if there’s a portable way to access this, though; you might have to write it differently for Z-machine and Glulx (since the word is stored differently in each).

2 Likes

You can portably check that by testing ((verb_word->#dict_par1) & 1).

I suppose you should do that check before calling DictionaryWordToVerbNum(), if you want to be really careful. For non-verb-words, DictionaryWordToVerbNum() will return $FF (Z-code) or $FFFF (Glulx).

Now that I look, you’re checking the return value of DictionaryWordToVerbNum() against dictlen, which doesn’t make sense. That value isn’t a dictionary table index; it’s a grammar table index.

2 Likes

Thanks. It made sense at 2:13 in the morning. Not sure why, now.

Beyond identifying the verbs, I want to get all the directions and their aliases. When you hit ‘e’ in the dictionary, how does one tell that corresponds to a direction? It looks like the parser relies on NounDomain, and I’m hoping there’s a better way to do this than spoofing being in the middle of parsing.

1 Like

The parser calls NounDomain(compass), which boils down to checking all the direction objects contained in the compass object.

The game could customize this, but I think that is very rare. You normally adjust compass directions by moving them into and out of the compass, either when play begins or (if you’re doing something really fancy) during play.

1 Like

Changing the set of directions is also pretty rare in I7, so you could just take all the direction names from the Standard Rules and put them into the table by hand. Not as elegant as finding all the verbs in the dictionary table, but in 95% of cases you’d only have to do it once. Adding a new verb is a lot more common than adding a new direction!

I had gotten my stubborn on and really wanted to do the directions dynamically.

Use DICT_WORD_SIZE of 15.

To say again-word: (- Glulx_PrintAnything(AGAIN1__WD); -).
To say again-alias: (- Glulx_PrintAnything(AGAIN2__WD); -).
To say oops-word: (- Glulx_PrintAnything(OOPS1__WD); -).
To say oops-alias: (- Glulx_PrintAnything(OOPS2__WD); -).
To say undo-word: (- Glulx_PrintAnything(UNDO1__WD); -).

Include (-
[ PrintCommandWords j wd dictlen entrylen entry;
    dictlen = #dictionary_table-->0;
    entrylen = DICT_WORD_SIZE + 7;
        wd = #dictionary_table + WORDSIZE + entrylen*j; 
 if ((wd->#dict_par1) & 1)
            print (address) wd;
];
-)

To say dictionary command entry (n - a number): (- PrintCommandWords({n}); -)

To decide what number is the dictionary length: (- #dictionary_table-->0 -)

To decide what number is the number of dictionary words of/for (obj - object): (- ({obj}.#name)/WORDSIZE -)

To say dictionary word (n - a number) of/for (obj - object): (- print (address) ({obj}.&name)-->({n}-1); -)

To say command words: (- PrintCommandWords(); -)

The dictionary list is a list of texts variable.

To decide what text is the expanded (T - a text) (this is expanded-text): decide on the substituted form of T.

To build the dictionary list:
  if the dictionary list is empty begin;
    now the dictionary list is { "[again-word]", "[again-alias]", "[oops-word]", "[oops-alias]", "[undo-word]" };
   now dictionary list is expanded-text applied to dictionary list;
   repeat with dir running through the directions begin;
      repeat with i running from 1 to the number of dictionary words for dir begin;
        let t be "[dictionary word i of dir]";
        unless t matches the regular expression "^direction", add the substituted form of t to dictionary list;
      end repeat;
   end repeat;
   now dictionary list is expanded-text applied to dictionary list;
   let wordlist be the substituted form of "[command words]";
   repeat with i running from 1 to dictionary length begin;
      let d be "[dictionary command entry i]";
      unless d is "", add the substituted form of d to dictionary list;
   end repeat;
   sort the dictionary list;
  end if;

lab is a room.

when play begins: build the dictionary list; say the dictionary list
3 Likes

For a true pedant, there is sadly no alternative to dynamically parsing the topic for a match to a direction name, because it is always possible that the author has included some ‘Understand...’ phrase that works through a routine called by the parse_name property of a direction object rather than just looking at the names in its name property.

We don’t want to use NounDomain() like the parser does because if the player types something that matches the name of more than one direction (such as directions) we don’t want NounDomain to ask a disambiguation question. So we go a bit deeper into the weeds and call SearchScope() instead…

EDIT1: This works for 9.3/6M62 and 10.1 or 10.1.2 I didn’t find any problem from using #dictionary_table in any of these versions

EDIT2: This version will only work for Glulx: the dictionary table is structured slightly differently in the Z-machine

3 Likes

After all that, I think it was a simpler solution just to fix the parser’s unhelpful .noverb grammar behaviour, as in this previously-mentioned thread!

EDIT: although that woudn’t prevent the player foolishly or inadvertently naming their spell the same as a single-word command such as ‘jump’- in which case they would still have to use ‘cast jump’ rather than just ‘jump’ to get it to fire.

EDIT2: either way, the minor issue will remain with both approaches that typing a ‘verbless’ command in response to a disambiguation question will incorrectly not be recognised as a new command (rather than an answer to the disambiguation question), and so will fail- probably with the response ‘I did not understand that sentence’- see also here.

2 Likes

To make this all as completely bulletproof as possible, as well as dynamically parsing the suggested spell name for matching direction names, I guess one should also dynamically parse it for a match to the opening token of any other ‘verbless’ grammar lines and disallow a match to those too. This is left as an exercise for the reader :laughing:

EDIT: This would be chiefly a concern for someone wanting to make an extension- most authors would be well aware whether they had other verbless grammar or renamed directions in their story… unless it was unknowingly included in an extension they were using…

2 Likes

That’s weird. It had been identifying it, in particular, as a problem. I must have had a different error elsewhere confusing the compiler in exactly the “right” way that it thought there was something wrong with that instead.

1 Like