X-Newsreader: Geminisoft Pimmy 3.2 Eng - www.geminisoft.com
From: "John Colagioia" <JColagioia@csi.com>
Newsgroups: rec.arts.int-fiction
Subject: [Inform] Dynamic Verbing (long)
Date: Mon, 01 Jul 2002 09:03:25 -0400
MIME-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: 8bit
NNTP-Posting-Host: ool-182f30fa.dyn.optonline.net
X-Original-NNTP-Posting-Host: ool-182f30fa.dyn.optonline.net
Message-ID: <3d205333@excalibur.gbmtech.net>
X-Trace: excalibur.gbmtech.net 1025528627 ool-182f30fa.dyn.optonline.net (1 Jul 2002 09:03:47 -0400)
Organization: ProNet USA Inc.
Lines: 222
X-Authenticated-User: jnc
Path: news.duke.edu!newsgate.duke.edu!nntp-out.monmouth.com!newspeer.monmouth.com!news.maxwell.syr.edu!nntp.abs.net!uunet!dca.uu.net!ash.uu.net!excalibur.gbmtech.net
Xref: news.duke.edu rec.arts.int-fiction:105613

This may sound like an extremely demented and/or stupid question,
but it's been bugging me for a while, now, and I can't see any
way to do it nicely.  So...uhm...help!

Basically, I want to lump at least some verbs into objects.
I'll give you two examples of where this might be useful or at
least interesting:

1)  In a game involving travel to far-off lands, an author
    *might* want to have a small section of the game which must
    be played in a foreign language.

2)  In a game where the player helps to develop the game world,
    it might be a nice gesture of trust to allow the player to
    name at least some of the verbs.

3)  Games with potentially boring activities might want to
    give players an in-game "macro" ability, which wouldn't be
    much different than the previous idea.

I realize the first example sounds like a lame excuse for an
unpleasant puzzle, but that's why it's just an example...

Anyway, I got it into my head that this might be nice.  So, I
reviewed my options.

Option #0:  Maintain multiple grammar tables, and perform evil
hackery to swap between the precompiled units whenever a
switch occurs.

Analysis:  I can't even dignify that.  I also don't yet know
the Z-Machine well enough for that to be even a *bad* option.

Option #1:  Create all the possibly-used verbs, and execute or
deny the verb action routine (VerbSub()) based on a global
"mode" value.

Analysis:  Icky globals, and the check has to be performed in
every verb routine.  Plus, it fails to allow for the player to
become involved.

Option #2:  Play with selfobj.grammar() so that it sometimes
converts the alien language into Informese.

Analysis:  If the entirety of Section 36 of the DM4 suggests
such grandly-scoped additions and changes, I'm not going to be
able to hack it (pun partially intended) in grammar().  Not in
any maintainable fashion, at any rate.  Further, this again
fails to allow dynamically-named verbs.

Option #3:  Create verb objects.  These would include the
name, grammar, and action routine associated with the verb.
The majority of the work can then be done in the stub
UnknownVerb(), which can loop through the set of verbs
(possibly choosing subsets based on current conditions), check
the name property, and then call the parser and relevant
action routine.

Analysis:  Conceptually nice.  Solves the dynamicism.  It'll
run very slow (since each turn requires looping through
verbs), but that's not so much of a concern for something that
may never make it to a game.  However, it's a bit more
difficult than even I was able to envision, and I was
expecting horrors.

Here's where I started:  An UnknownVerb() routine to loop
through the verb objects.

[ UnknownVerb word i ;
 objectloop (i ofclass VerbObj)
	if (WordInProperty (word, i, name) && i.vparse ())
		return (i.sub ());
 rfalse;
];

If the object has the right verb name, and its parsing
method succeeds, it calls the action routine.  Easy enough,
right?

Now comes the nastier stuff.  We need a VerbObj class.  I
suppose I'll just dump that in, directly, and explain as I
go:

Class VerbObj
  with	name	0,
! This would be where all the verb synonyms sit, not unlike
! the beginning of Inform's Verb syntax.
	sub	[ ;
		 "You cannot do that quite yet.";
		],
! That'd be the action routine.
	number	0,
	best	0,
	grammar	-100 0,
! The grammar uses the same token codes as the Inform standard
! library, plus -100, which represents the end of a parse (and
! which I call WORD_END in my actual code).
	vparse	[ i max len ;
! Here's the actual parser for the verbs.  Actually, it
! finds out if there are multiple parses for the same verb,
! and calls the "line parser" for each such variation.
		 self.best = -999;
		 if (self.&grammar-->0 ~= MULTI_TOKEN)
			return (self.lparse (grammar) == 0);
! A single line is represented by a zero-preceded array.
		 max = self.#grammar;
		 for (i=1:i<max:i++)
			{
			 len = self.lparse (self.&grammar-->i);
			 if (len == 0)
				{
				 self.number = i;
				 rtrue;
				}
			 else if (len > self.best)
				self.best = len;
			}
		 rfalse;
		],
	lparse	[ line gl wc pw wd loc obj ;
! And here's the aforementioned line parser.
		 noun = nothing;
		 second = nothing;
		 gl = self.#line;
		 wn = 1;
		 for (wc=0:wc<gl:wc++)
! There is, of course, a distinct similarity between this
! routine and a typical parse_name() method, since they do
! approximately the same thing, just from different
! perspectives.
			{
			 pw = self.&line-->wc;
			 wd = NextWordStopped ();
			 switch (pw)
				{
				 WORD_VRB:
! Another magic constant, of course.  This verifies that
! the first word is, indeed, the verb, but may also allow
! future variations where the verb doesn't come first.
					if (WordInProperty (wd, self,
						name) == false)
						return (wn - gl);
					verb_word = wd;
				 NOUN_TOKEN, HELD_TOKEN, MULTI_TOKEN,
				 MULTIHELD_TOKEN, MULTIEXCEPT_TOKEN,
				 MULTIINSIDE_TOKEN, CREATURE_TOKEN:
! A fairly standard noun parser, except...
					-- wn ;
					loc = location;
					do	{
						 obj = NounDomain (loc,
							actor, pw);
						}
					until (obj ~= REPARSE_CODE);
					if (obj == nothing)
						return (wn - gl);
! ...we also need to fill in the proper support variables, noun
! and second.  I also increase parameters, should it be needed
! elsewhere.
					if (noun == nothing)
						noun = obj;
					else	second = obj;
					++ parameters;
				 WORD_END:
! This just verifies that the line has, indeed, terminated.
					if (wd == -1)
						return (0);
					while (NextWordStopped () >= 0)
						;
					return (wn - gl);
				 default:
! Finally, we allow arbitrary words, which Inform would call
! "prepositions," I believe.
					if (wd ~= pw)
						return (wn - gl);
				}
			}
		],
	;

None of that is earth-shattering, I don't think.  It's ugly
and tedious, but not "evil" code.  It even works.  I can
now create:

VerbObj French_Take "prends"
  with	name	'prend' 'prends' 'prendre',
	sub	[ ; return 'take'; ],
	grammar	WORD_VRB MULTI_TOKEN WORD_END;

and life goes on as you'd expect, with the player now able
to prends things to move them into inventory.

Since I only need to fill in verb synonyms, an appropriate
routine, and a grammar, this can even be done at run-time,
which was, indeed, one of my original goals.

The problem?  Since I wrote my own parser, and since my parser
is "nice" and "simple" (relative to what it could have been),
it also is very ungraceful with its error handling and
recovery.

Most errors are replied to with "That's not a verb I
recognize," as you might guess.  This is less flexible, and
far less friendly to the player, since it's out-and-out
misleading, which is a really bad thing.

Additionally, I definitely don't know how to use
MULTI_TOKEN, even after reading through the "real" parser,
since the most sophisticated thing I can do is catch the
player in an infinite loop of asking "What do you want to
prends?"  That's also bad.

So, my question--for anyone who has been able to read this
far down such an uninteresting tirade:  Is there any way
to "leverage" the pre-existing parser to perform the tasks
I want, without having to do something horrid like rewrite
the Inform parser or hack the usual grammar table?

I should note, by the way, that the fact that I got as far
as I did in my little quest impressed me greatly.  I knew
that Inform was flexible, but I would never have guessed
that it could be twisted into teeny-tiny pretzel-shapes...
