Sunday, June 30, 2013

Container Theory Part II

Ok, so not long after writing that last post, I remembered one of the main reasons I have long associated "transparent" with "container".

In the example of a snowglobe, if we define it as a non-container, transparent object, it'll work fine with DoPutIn. A container, transparent object would need additional before routines to disallow it. This is part of the reason why yesterday's view of containers sounded appealing to me.

Still, that doesn't change the fact that DoLookIn demands that objects be containers. What's the point of a snowglobe that you can't look into? Anyhow, instead of trying to fix what is and isn't a container, I think I'm just going to fix DoLookIn's grammar.

I could try to do it on the grammar level, using Roodylib's "grammar helper" stuff to only allow containers and transparent items to work on the grammar pass, but I don't like forcing Roodylib users to use extensions like that so I'm probably just going to change DoLookIn's container check to an object token and add all necessary additional code to DoLookIn itself.

On the plus side, this should make coding windows (the glass kind) more intuitive to future authors. On the down side, maybe this change will have some ramifications down the road.

I mean, I could go back to my old container assumptions, but since I've already learned that others find other definitions more intuitive, I don't think that's the right path. I mean, the holy grail of an IF language is to make everything as intuitive as possible.

Container Theory -or- How Do You Code a Bucket?

So recently, I've been helping with a contents-listing problem during the >INVENTORY command involving coat pockets. Fixing that involved a change to WhatsIn and SpecialDesc- which I had intended to write up here, if it weren't for the issue that I want to talk about today (in fact, after WhatsIn and SpecialDesc, I have another thing I want to write about, so hey, the next entry will be a nice, multi-part post).

It turns out that my updated code still isn't working for that other person, and my hunch is that it has to do with the fact that my code has been slowly changing how containers are dealt with. Today, I figured I should finally run my designs past Kent. Eventually, we unearthed the point of contention where we disagreed on default behavior.

In Kent's view, a bucket coded in Hugo should have the following attribute definition:

    is container

While in my mind, it should be defined as such:

    is container, open

I think this might also be the difference in the coat objects as defined by me and the fellow I'm trying to help (since my new SpecialDesc code relies on the open attribute being used). To those who prefer the first definition, adding "open" must seem like overkill. As far as whether-contents-can-be-seen, I agree, as far as unopenable containers are concerned, it can be assumed that they are either open or transparent (otherwise, it's useless for them to be a container).

I was going to say that as far as other container behaviors go, I was worried about things like not-open, unopenable, transparent objects, but I had a Hugo-understanding breakthrough just now as I write this. I glanced over at the character class definition and was reminded that characters are transparent without being containers. I think part of my container-understanding problem is that I have so strictly associated "open" and "transparent" with containers (since thinking of transparent examples often make you think of something like a glass jar), when it's better to pretend that "transparent" has nothing to do with containers and remember that "open" only exists to work with "openable".

Anyhow, I feel pretty dumb that I have been so unclear on the concept for this long, but I'll try to update HbE so future coders don't fall into the same traps I did. Also, I still think that some of the hugolib contents-checking conditionals could be more concise, so I'm going to try to do something about that.

While I'll quickly admit being wrong on this issue, it's interesting that something as simple as containers can so easily catch people (maybe most people won't make the same assumptions I did, but I'm sure some do). I imagine the container code I wrote in ZIL recently is just as flawed. Ah, well.

Thursday, June 27, 2013

New newmenu stuff

So, some days ago- I've taken my time getting around to write this blog post about it- I was working on porting John Menichelli's HugoZork to Roodylib. One of the last additions I plan to do involved transferring his menu code over to my newmenu format. This, in turn, got me thinking about newmenu for the first time in a while.

alt_title property


First off, his menu pages often have a different title from the link name that led to them. In the past, with newmenu, I've just kept an extra CenterTitle call in the page text. This has the downside of possibly getting double-titled in a simple terminal interpreter. Converting HugoZork was the straw that made me go, eh, I might as well add another property to menu option objects. Behold, the alt_title property (I actually gave it an alt_name alias in case I forget what I called it along the way)!

(Also, hey, look, I found a better way to share code)

option amusement_option "For Your Amusement/Words To Try"
{
    in main_menu
    alt_title "For Your Amusement"
    menu_text
    {
        "Have you ever:\n"

        "\_  ...opened the grating from beneath while the leaves were still
        on it?"
        "\_  ...tried swearing at ZORK I?"
        "\_  ...waved the sceptre while standing on the rainbow?"
        "\_  ...tried anything nasty with the bodies in Hades?"
        "\_  ...burned the black book?"
        "\_  ...damaged the painting?"
        "\_  ...lit the candles with the torch?"
        "\_  ...read the matchbook?"
        "\_  ...tried to take yourself (or the Thief, Troll, or Cyclops)?"
        "\_  ...tried cutting things with the knife or sword?"
        "\_  ...poured water on something burning?"
        "\_  ...said WAIT or SCORE while dead (as a spirit)?\n"

       ! "\nPress any key for more..."

       ! pause
            CoolPause(1)
        CenterTitle("Words To Try")

        "Words you may not have tried:\n"

        "\_  HELLO (to Troll, Thief, Cyclops)"
        "\_  ZORK"
        "\_  OIL (lubricate)"
        "\_  XYZZY"
        "\_  WALK AROUND (in forest or outside house)"
        "\_  PLUGH"
        "\_  FIND (especially with house, hands, teeth, me)"
        "\_  CHOMP (or BARF)"
        "\_  COUNT (candles, leaves, matches, blessings)"
        "\_  WIN"
        "\_  MUMBLE (or SIGH)"
        "\_  LISTEN (especially to the Troll, Thief, or Cyclops)"
        "\_  REPENT"
        "\_  WHAT IS (grue, zorkmid, ...)"
        "\_  YELL (or SCREAM)"
        "\_  SMELL\n"

        CoolPause(1)
    }
}

So, the name of the object represents how the choice will be listed in the menu itself, and the alt_title property is how the page is titled when you are reading it (if you don't provide an alt_title property, it just uses the link name).

TopPageMargin routine


I also added a routine called TopPageMargin. It is to newmenu what LinesFromTop is to Roodylib. I give it its own routine for two reasons- 1) newmenu does not require Roodylib and 2) I find that I sometimes like menu pages to have a slightly different margin than the game itself.

Right now, TopPageMargin defaults to returning 2 so page text automatically starts 2 lines down from the top. If your menu page text already does its own carriage returns for proper spacing, you can always replace TopPageMargin to return 0.

Glk menus


I also tweaked the glk behavior for better detecting when there are too many options and the menu has to be drawn by the "cheap" non-windowed method. Right now, it only draws the cheap menus as needed, so it's entirely possible that mid-menu, it'll jump from one kind to the other. I'm considering having it decide before it even gets started if it'd ever possibly use the cheap menu (and then just use that throughout), but that leaves the window open for game crashes if the player resizes the window mid-menu.

Other thoughts


Looking at the code again, I was reminded how newmenu is currently a mix of array and object hierarchy design. Given that I've done much more object-hierarchy organized design since I initially wrote newmenu, I started wondering, hmm, maybe I should just switch newmenu to doing everything by object hierarchy.

After thinking about it, my initial conclusion was that all of the necessary object-organization routines would complicate things more than necessary, and the end result would probably be more memory-intensive than the existing system so I've given up on that idea for now. Still, it seems like it might be possible to design something that only rearranges itself as necessary and largely works without writing values to anything.

I'll probably get back to this as some point when I have something else I really should be doing instead.

Thursday, June 20, 2013

SpeakTo stuff

So, yesterday, I was adapting John Menichelli's Hugozork to Roodylib, just because I'm always curious to see how well Roodylib plays with already-written games. Initially, there was some ParseError replacement code for the Loud Room that I was able to move to Roodylib's PreParseError routine, but I noticed that some of it didn't do the Loud Room's echo behavior quite right so I spent some time fixing that.

While testing that code, I realized that the engine-called SpeakTo was skipping all of my glorious code completely (SpeakTo is called directly both when you give characters commands, like: >CLOWN, HELLO or just when you type an object with no verb, like >ROCK).

I decided that SpeakTo should have to do location and player before checks, to better allow response configurations for situations just like the Loud Room. Here is my current SpeakTo:
replace SpeakTo(char)
{
local a, v, TryOrder, IgnoreResponse, retval, stay, same, different
#ifset USE_CHECKHELD
if verbroutine = &DoDrop_CheckHeld
verbroutine = &DoDrop
elseif verbroutine = &DoPutIn_CheckHeld
verbroutine = &DoPutIn
#endif

#ifset VERBSTUBS
if verbroutine = &DoHelpChar and object = player
{
verbroutine = &DoHelp
object = nothing
}
#endif

#ifset USE_CHECKHELD
ResetCheckHeld
#endif

#ifset DEBUG
if debug_flags & D_PARSE
{
print "\B[Speakto("; char.name;
if (debug_flags & D_OBJNUM)
print " ["; number char; "]";
print ") verbroutine="; number verbroutine;
print ", object="; object.name;
if (debug_flags & D_OBJNUM)
print " ["; number object; "]";
print ", xobject="; xobject.name;
if (debug_flags & D_OBJNUM)
print " ["; number xobject; "]";
print "]\b"
}
#endif

v = verbroutine
a = actor
actor = player
verbroutine = &SpeakTo
retval = player.before
actor = a

if retval
{
#ifset DEBUG
if debug_flags & D_PARSE
{
print "\B["; player.name;
if debug_flags & D_OBJNUM
print " ["; number player; "]";
print ".before returned "; number retval; "]\b"
}
#endif
return retval
}

retval = location.before
if retval
{
#ifset DEBUG
if debug_flags & D_PARSE
{
print "\B["; location.name;
if debug_flags & D_OBJNUM
print " ["; number location; "]";
print "before returned "; number retval; "]\b"
}
#endif
return retval
}

verbroutine = v

if char is not living
{
ParseError(6)  ! "That doesn't make any sense."
return
}

AssignPronoun(char)

! Handle player/typist-related ParseError messages:
if char = player
Message(&Speakto, 1)    ! "Stop talking to yourself..."
elseif not ObjectisKnown(object) and not FindObject(object, location)
ParseError(10, object)
else
   stay = true

if not stay
  {
  speaking = 0
  return
  }

if char is unfriendly
IgnoreResponse = true
else
{
! In the event of:  >CHARACTER, GO NORTH.  GET THE THING.  GO WEST., etc.
if not FindObject(char, location)
{
speaking = char
run char.order_response
return true
}

same = (char = speaking)

select verbroutine
case 0                  ! Just the character name is given,
! so just "X is listening."
{
if not char.order_response
Message(&Speakto, 2, char)
retval = true
}

#ifclear NO_VERBS
case &DoHello           ! Note the ampersands ('&')--or else
{                       ! the routines themselves would run
if not char.order_response
{
if char is not unfriendly
{
! "X nods hello."
Message(&Speakto, 3, char)
retval = true
}
else
{
IgnoreResponse = true
}
}
else
retval = true
}

case &DoAskQuestion
return Perform(&DoAsk, char, object)

case &DoTalk
{
if xobject
ParseError(6)
else
return Perform(&DoAsk, char, object)
}

case &DoTell
{
if object = player
return Perform(&DoAsk, char, xobject)
else
TryOrder = true
}
#endif  ! ifclear NO_VERBS

case else
{

! If the character can respond to a request, this should be dealt with by
! an order_response property routine; order_response--if it exists--should
! return false if there is no response for the given verbroutine

TryOrder = true
}
}

if TryOrder
{
if (not char.order_response)
IgnoreResponse = true
else
retval = true
}

different = (speaking ~= char)

! This same/different local variable stuff allows for certain
! orders to end conversations. If your order_response code clears
! the speaking global, this code prevents it being reset.

if retval and not (same and different)
speaking = char

if IgnoreResponse
{
if not char.ignore_response
Message(&Speakto, 4, char)      ! "X ignores you."
speaking = 0  ! clear the speaking global
}
return retval
}
Now, there are a couple problems. One, currently, both the location and player code have no idea what command the player is trying to order done, as I had to change the verbroutine to &SpeakTo for easy catching. Further complicating things, is that for the player object, I had to change the SpeakTo-determined actor global, too.

Ideally, I'll be able to figure out a way to organize all of these values so authors can have as clear an idea as possible about what was typed, and hopefully I'll be able to do it without the creation of new globals (although that isn't a big deal and I'll probably end up doing it anyway).

Also, let it be said that this new code is very lightly tested so there might be a gaping bug I haven't found yet.