Wednesday, October 19, 2016

GENERIC-OBJECTS as conversation (ASK/TELL) fodder

Note: None of the code in this post adheres to the formatting recommended to me for ZIL programming so I apologize if the look of it drives anyone crazy. While debugging the problem at hand, I use an indenting system I find most readable, but if I ever release any game source, I'll try to format it as suggested. I also might write another post here at some point sharing what those format conventions are.

The ZIL manual lists two ways for setting up ASK/TELL conversations:

  1. The (EVERYTHING) syntax addition allows for verb definitions where any object is accepted, whether or not it is scope.  Like so:
    <SYNTAX ASK OBJECT ABOUT OBJECT (EVERYTHING) = V-ASK>
  2. It also mentions GENERIC-OBJECTS objects that are out of scope and can't be physically interacted with.  It specifically mentions Deadline's "new will," the missing mystery document a player might be inquiring about.  Despite not being physically available, these objects are always in scope.

Originally, I thought was a little unclear why ZIL needed both methods, as I figure that if the (EVERYTHING) syntax works, you could just stick those objects in a room no one will find them and, hey, no one will interact with them.  It occurred to me while writing this, though, that an author might want different error messages for trying to interact with tangible objects compared to idea/topic objects.  You don't want >EXAMINE FREEDOM to respond with "You don't see that here." Unless your game is kind of an ass about freedom.

Anyhow, the current ZIL/ZILF compiler doesn't support the (EVERYTHING) method, so it was recommended to me that I try to handle everything with GENERIC-OBJECTS.  I imagined this was going to be sort of a headache.  I figured I'd have to write fancy code to take GENERIC-OBJECTS out of scope when they had a real object counterpart within scope; if I had a flashlight in my game, I'd have to have a GENERIC-OBJECTS object for it for when players ask NPCs about it when it is another room, but when it is in scope, I don't want "Which flashlight do you mean, the flashlight or the flashlight?" responses.  This ended up not being a problem, but I'll get to that later.

My lack of enthusiasm to play around with all of this resulted in a several month break from ZIL code, but I got the itch back recently.  Of course, it didn't help that I initially got off on the wrong foot because I had forgotten the name of the scope thing I needed so I just quickly skimmed the ZIL model, saw another type of scope object called LOCAL-GLOBALS, and thought, ah, that must be it.  Unfortunately, LOCAL-GLOBALS objects are something else, something better used for tangible things that show up in several rooms like a river or something.

After wasting a good couple hours on that, Jesse McGrew set me off on the right GENERIC-OBJECTS path.  Right away, my disambiguation worries became a non-issue because it turns out GENERIC-OBJECTS are only parsed at a last resort so they never have the same "parser weight" as an actual object in the same room.

The only remaining issue was that while interacting with a non-present object properly responded with "You don't see that.", the message took up a turn.  Consistency among error messages is one of my many goals with any IF language I work with.

Luckily, Jesse was there to the rescue again and pointed out the "hook" where we could fix that.  He gave me this code:

First off, if one were to use this code, they'd put it before "parser.zil" is included. REPLACE-DEFINITION tells the compiler, "hey, when we get to this code, we'll use this instead" (I'm probably getting this wrong on some technical level but it's how I took it).  You can only use REPLACE-DEFINITION on certain hook routines, but as this post shows, it can be useful they're there.

The interesting thing about this code is, as you can see by the name, it runs after the command has been performed but we still have time to say, "change the command to INVENTORY" (so it doesn't take a turn).  This is another one of the things I find endearing about ZIL; an impressive amount of the game loop can be modified through code (unlike many later IF languages where more aspects of the game loop are set in stone by the game engine).

Jesse wrote the above code without testing.  It probably worked just fine right out of the box, but as far as I could read it, I thought it seemed a little wrong so I instantly started re-writing it.  It took several attempts, but I eventually got something that worked:

At this point, it occurred to Jesse that instances where the PRSO (the direct object) or PRSI (the indirect object) are 0 could run into problems, specifically with the "<IN? , PRSI ,GENERIC-OBJECTS>" check.  I figured the worry was the Vile Zero Error From Hell.  First off, out of curiosity, I wanted to see if my interpreters would catch it.  As many interpreters today straight out ignore Vile Zero Errors From Hell, finding one that even pointed it out took several tries.  Eventually, I got DOS Frotz to say, hey, something is wrong.

So yes, I had to add some code to check for 0 values.  My final code ended up looking like this:

Basically, this code says, GENERIC-OBJECTS should not take a turn when used as direct objects, and if it's an indirect object, only take up a turn if it's part of ASK/TELL.

Originally, I tried to spread out my code a bit more, but the way these DEFMAC things seem to work, they really only allow for one element.  That's why my solution basically is one conditional with several other conditionals wrapped inside it.  I asked Jesse about it, and it turns out there's another command, BIND, which allows for routines-within-routines.  Not only that, but the BIND code can have its own local variables... and not only that, but you can also give those local variables starting values, something you can't do with "AUX" variables for your random routine.

In the code:
<BIND ((A 1) (B 2) Z T W) ...>
A would have a starting value of 1 and B would have a starting value of 2.

Anyhow, here is a final version of the above routines, using BIND to make the code easier to read (hopefully):

Setting up this conversation stuff should be the last speedbump for a while, at least until I start working on QUEUEs (ZIL's version of daemons/events) although I expect that all to work fine (with just some extra design effort on my part). I may also write a menu system at some point, mainly as a distraction to keep myself from working on, gasp, ACTUAL GAME CODE.

EDIT:  Okay, it turns out I wasn't done with the above code.  I was going through all of parser.zil's verb routines making sure that applicable routines gave "You don't see that." type responses for GENERIC-OBJECTS.  I was reminded that >THINK ABOUT [object] is a default verb there, which of course should be allowed for taking up a turn.  In testing out the code, I noticed that behavior would be broken after a couple turns.  I had thought BIND treated its variables as local variables if not given a starting value, but no, they act more like global variables (so, ERR, once set as 1, would always stay 1).  Here is the fixed code:


Sunday, February 14, 2016

Announcing the ZIL Notepad++ add-on

Some people were unable to use my standalone Notepad++ package because they already had Notepad++ installed.  I finally got around to putting together a package adding (most of) its functionality.  Just download the following file and follow the instructions in the .pdf file!

https://drive.google.com/file/d/0B_4ZXs4Z_yoWeVplT0dPX1ZpOUU/view?usp=sharing

It includes the compiler, an interpreter, and library files, so once installed, you'll be ready to start writing your ZIL game!  As mentioned in the .pdf, all of the new commands will be under the Macros tab when you're done.

Monday, January 11, 2016

First Project! (Part 3)

Ok, I remembered a couple other things I could talk about.

>EXAMINE ORGAN-GRINDER

The first room has an organ and an organ-grinder.  I wanted to allow the player to refer to the grinder as "grinder," "organ grinder," and "organ-grinder."  What I found, though, is that ZILF did not like ORGAN-GRINDER as a synonym; it did not like the dash in the definition.

For this problem, I needed Jesse McGrew's help.  He gave me the following code to be inserted before "parser.zil" is included.

The above code looks for a dash as the sixth character of a word then replaces it with the word of our choice.  As you can see in the comments, it originally put in the fake synonym "O.GRINDER" but since there really wasn't any reason it couldn't just be changed to "GRINDER," I changed it to that.

Anyhow, a parsing trick like that could be very useful to someone at some point!

Ok, I was thinking I could also write something about DESCFCNs (where routines are used for object descriptions), but I see now that it was shown pretty well in the juggler code I shared in the other entry.

So yeah, that's all my ZIL for now!

First Project! (Part 2)

(I broke this up into 2 posts because the way I embed the example code was starting to make the HTML all funky)

Inventory!

So, ZILF's library defaults to "tall" inventory listings, where each item is given its own line.  The transcript uses the "wide" format so I had to edit my own version of V-INVENTORY.




In a bit of additional transcript weirdness, the circus ticket is listed as "a circus ticket" in the inventory and just "ticket" everywhere else.  Since I was editing V-INVENTORY anyway, I just put in a condition for printing "a circus ticket" instead of using its default name.  Since this game doesn't need it, I also took out the light and container checks.

You'll notice how MAP-CONTENTS loops through every child of the second argument, setting it to the first argument.  I use it once to get the number of children of the player so I can adequately add punctuation the next time around when everything is printed out.

>JUGGLER, TELL ME ABOUT THE DOG

Originally, I was hoping to finish the transcript game in time to submit it to Marius Müller's "Neinth annual New Year's Minicomp."  I was saving the coding of the juggler's actions for last as I imagined it'd take the most work (some daemon/queue stuff along with the juggler dropping things and reacting to whichever objects the player has grabbed), but before I got to that, I ran into another issue.

It turned out that the EVERYWHERE grammar token (ZIL calls them "syntax tokens") hasn't been implemented yet.  EVERYWHERE allows verbs to refer to any known, referable object, pretty much existing mostly for ASK and TELL.  Without EVERYWHERE, I could only ask NPCs about objects in immediate scope.

The workaround for this involves making fake global objects just for the purpose of conversation.  While pursuing this method, I noticed GENERIC-OBJECTS wasn't working 100% as it should.  GENERIC-OBJECTS are objects that can be referred but not seen or touch (the manual lists the "new will" in Deadline as an example).  I found that my player could see them just fine!

The fix for this involves a couple steps.  First off, I had to edit VISIBLE? so it did not allow GENERIC-OBJECTS along with GLOBAL-OBJECTS (which can be seen from everywhere).  Unfortunately, VISIBLE? is only called when the command has a pronoun, so the following code needs to be added to all applicable verb routines:

<COND (<IN? ,PRSO ,GENERIC-OBJECTS>
<CANT-SEE>
<RETURN>)>
(CANT-SEE is just a routine for printing "You don't see that here.")

This is mostly on the right track although there is still one remaining issue.  Referring to an object that is actually out of scope doesn't take up a turn, but referring to an object in GENERIC-OBJECTS does.

What's next?

I still have to go through the rest of the applicable verb routines and add the above code, even though it's likely I'll use LOCAL-OBJECTS instead (where you can limit objects' access to certain rooms).  Then I'll have to make sure they never clash with real objects.  After that, I can get to work on coding that juggler!

That all said, now that I've missed the deadline for the New Year's comp, I have some other things I need to work on, so the Ballyhoo Sample Transcript Game is going on the backburner for a little bit!

Well, actually, I just remembered one more thing I could write about, but I'll save that for next time.

First Project! (Part 1)

My ZIL plan for a while has to begin my journey by implementing one of the sample transcripts from one of the Infocom games; I have other project ideas that might use ZIL down the road, but a lot of these transcripts involve several features and it always just seemed like a fitting way to get to know the language.

For a while, I delayed the project since most transcripts involved commands to characters, and before ZILF 0.7 (and its Adventure example code), I had no idea how to code them.  I have to say that I like how ZIL/ZILF handles it now!

I was torn between trying to do the sample transcripts from either Ballyhoo or Lurking Horror, as those were the first Infocom games I ever purchased and have a special fondness for them.  Since the Frankenstein narrative of Lurking Horror's sample transcript was better suited to Halloween, I opted for the Ballyhoo transcript.

Ballyhoo sample transcript
Now, in trying to actually execute these transcripts, one gets the impression that they weren't ever actually coded (no big surprise there), nor do they have the best internal game logic.  I still have to come up with some good in-game excuse to not allow the player to just give his ticket to the organ grinder at the beginning of the game.

The juggler is here, dropping things.

Among other things, the transcript seems to be in BRIEF mode and the juggler character in particular has alternate text when you revisit his room (and it doesn't seem like something that'd be coded as a daemon/queue).  For a short while, it made me temporarily forget Infocom's actual default behavior (where no room objects are listed in revisited rooms) and I almost set upon rewriting my ZILF library's code.  In the end, I realized that the transcript was doing something that no actual Infocom game did.  Just the same, I was able to code it:

To get that code to work, I had to edit DESCRIBE-PLACE so it didn't set the TOUCHBIT flag (as TOUCHBIT is also set by the GOTO routine).  The first room of the game still has to be given TOUCHBIT by hand, but other than that, it should be good.

The spacing of the transcript also doesn't quite match an actual Infocom game.  Of course, this is for manual typography purposes, but it reminds me how optimal default spacing is always an interesting concept to me (and whether it is important for authors to easily change it).

You crawl under the hedge and find yourself...

My first thought pertaining to the hedge-crawling text was that I would use the M-ENTER check as it is used by the ZIL manual to print entering-rooms messages.  Even though the game didn't require it, I checked to see if the code supported different-messages-depending-on-where-the-player-is-coming-from.  Due to setting the HERE global before the M-ENTER code was called, it wasn't possible.  Easy enough to fix, though.

In the end, I decided that since this message was directly tied to the hedge, I'd just write a bunch of hedge code instead.


Once the player has crawled under the hedge, he or she can go west back onto the street without typing >CRAWL UNDER THE HEDGE but leaving the street always involves the command (in trying to be faithful, I imagined Infocom to somewhat be a dick about it).

Putting the hedge object in location GLOBAL-OBJECTS makes sure it is available from every room in the game.

I made that HEDGE-CRAWL routine so that players can re-enter the street with a simple >GO WEST (and go to the right location, depending on where they left from).  That was pretty fun to figure out.  Notice how the code sets the local variable (DESTIN) to the applicable room and then just ends with saying the variable again.  This makes the routine return the value of the variable, sending the player to the correct room.

To be continued!

Sunday, January 10, 2016

Hello, Sailor!

Welcome to my ZIL blog!  In the future, I will share my thoughts and experiments as I play with Jesse McGrew's ZILF compiler.

If you're not familiar with ZIL, the "Zork Implementation Language," I'd recommend reading the ifwiki's overview.  For those that would like to "play along at home", be sure to check out some of the links I've set up in the sidebar!