Thursday, March 28, 2013

Empty Progress

So, I got a working prototype of my solution. It uses a mix of Roodylib's grammar routine helper stuff and object classes.

First, let's look at our new grammar definition:
verb "empty", "unload"
    *                                                       DoVague
#ifset NEW_EMPTY
    * (CheckEmpty) "on"/"onto" "ground"/"floor"             DoEmptyGround
#else
    * object "on"/"onto" "ground"/"floor"                   DoEmptyGround
#endif
    * multi "from"/"off"/"on"/"in" parent                    DoGet
    * multi "offof"/"outof" parent                         DoGet
    * multi "from" "offof"/"outof"/"on"/"in" parent         DoGet
! Send >UNLOAD OBJECT to DoEmptyoOrGet, which dispatches to DoEmpty or DoGet
!    * object                                                DoEmpty
#ifset NEW_EMPTY
    * (CheckEmpty)                        DoEmpty ! DoEmptyOrGet
#else
    * object                DoEmpty
#endif

In no cases does it run DoEmptyOrGet anymore as I think it's so general that it just leads to trouble. Next, let's take a look at our object classes:
#ifset NEW_EMPTY
property empty_type

class unheld_to_player
{
    empty_type NOTHELD_T
    before
    {
        object DoEmpty
        {
            local a, b, obj, xobj
            local thisobj, nextobj

            CalculateHolding(object)

            if object is container, openable, not open
            {
                VMessage(&DoEmpty, 1)           ! "It's closed."
                return true
            }

            if object is not container, platform
            {
                ParseError(12, object)
                return false
            }

            if not children(object)
            {
                VMessage(&DoEmpty, 2)           ! "It's already empty."
                return true
            }

            thisobj = child(object)
            while thisobj
            {
                nextobj = sibling(thisobj)

                print thisobj.name; ":  ";

                if thisobj is static
                    VMessage(&DoEmpty, 3)    ! "You can't move that."
                else
                {
                    a = player.holding
                    b = thisobj
                    obj = object
                    xobj = xobject

                    if Perform(&DoGet, b)
                        player.holding = a + b.size
                    else
                        player.holding = a
                    object = obj
                    xobject = xobj

                    if b not in object
                        object.holding = object.holding - b.size
                }

                thisobj = nextobj
            }

            run object.after
            return true
        }
    }
}

class held_to_player
{
    empty_type HELD_T
    inherits unheld_to_player
}

class held_to_ground
{
    empty_type HELD_T
}

constant NO_EMPTY_T 16

class no_empty
{
    empty_type NO_EMPTY_T
}
If it's not self-explanatory, that consists of a class that represents objects that must be held and empty out to player (like unloading bullets from a gun), objects that must be not held and empty out to player (like unloading a laundry machine), objects that must be held and empty out to ground (like a bottle of liquid), and objects that can't be emptied (for containers and platforms that deserve a "You can't empty that." message).

Here is the grammar helper routine:
routine CheckEmpty(obj)
{
    if obj.empty_type
    {
        TOKEN = obj.empty_type
        if not CheckObject(obj)
        {
            return false
        }
        elseif obj.empty_type = NO_EMPTY_T
        {
            ParseError(12, obj)
            return false
        }
        return true
    }
    elseif not CheckObject(obj)
        return false
    return true
}
#endif NEW_EMPTY

Lastly, whether or not players use this new system, I think Roodylib will replace DoEmpty to switch around the platform/container check before the children check:
 replace DoEmpty
{
    local a, b, obj, xobj
    local thisobj, nextobj

    CalculateHolding(object)

    if object is container, openable, not open
    {
        VMessage(&DoEmpty, 1)           ! "It's closed."
        return true
    }
    if object is not container, platform
    {
        ParseError(12, object)
        return false
    }
    if not children(object)
    {
        VMessage(&DoEmpty, 2)           ! "It's already empty."
        return true
    }

    thisobj = child(object)
    while thisobj
    {
        nextobj = sibling(thisobj)

        print thisobj.name; ":  ";

        if thisobj is static
            VMessage(&DoEmpty, 3)    ! "You can't move that."
        else
        {
            a = player.holding
            b = thisobj
            obj = object
            xobj = xobject

            if player not in location and
                (parent(player) is platform or
                    parent(player) is container) and
                not xobject:

                Perform(&DoPutIn, b, parent(player))
            else
                Perform(&DoDrop, b)

            object = obj
            xobject = xobj
            player.holding = a
            if b not in object
                object.holding = object.holding - b.size
        }

        thisobj = nextobj
    }

    run object.after
    return true
}
 EDIT: Just throwing in that, yeah, I know my code does nothing about the "multi from/outof/etc xobject" grammar that points to DoGet, but I think that would go to DoGet in most cases anyway and I'm comfortable with people having to write their own before routines to catch the rest.

full of "empty" problems

First things first, even with the next Hugo Open House months away, I've been thinking a bit about the next one's theme. I think all involved with last year agree that it was even more of a hassle to get things done, even with the extra time. I like to devote the rest of the year to real projects, though, so I don't really want to move the Open House earlier.

My recent thought was that this year's theme should be ports. Part of the inspiration for this was that Johnny Rivera and I have had our eye on Cardinal Teulbach's Hugo 1.2 games. I know Robb Sherwin has had his eye on some BASIC games, and in general, there are just a vast number of outdated game systems out there with games that could use new life.

So, yesterday, instead of actually waiting for the Hugo Open House to come around, I thought I'd dip my toe in the porting waters to see what it feels like. I started with Cardinal Teulbach's SceptreQuest which in itself is a port. Originally, I was looking at the original BASIC source and thought I'd be writing a new version of DescribePlace (which could be conceivably be used other similar BASIC ports), but when I opened up Teulbach's version, I was reminded that they are two very different games. His game is more of a re-imagining.

Anyhow, I'm mostly done with the reimplementation of it all, but I've been tidying up a couple things before I release it (even to what degree I release it is uncertain since Teulbachs was kind of hardcore about his usage license).

Long story short, in testing some of the various containers and platforms I have in the game, I have paid more attention than usual to the DoEmpty and DoEmptyOrGet routines. It is my current conclusion that they are in dire need of being fixed.

Right now, typing >EMPTY <non-held, non-container-or-platform object> results in that object being picked up.

There's also this bit in DoEmpty:
    if not children(object)
    {
        VMessage(&DoEmpty, 2)           ! "It's already empty."
        return true
    }
       
    if object is not container, platform
    {
        ParseError(12, object)
        return false
    }
I have a feeling that in earlier versions, this code may have been switched, as that ParseError message is successfully called in games like Spur and Guilty Bastards. I imagine that DoEmptyOrGet was added and DoEmpty was mixed around in the creation of Future Boy! to solve some problem, but it unwittingly opened some other problems.

Of course, sometimes game design  relies on player's not doing nonsensical things (like trying to empty or unload average non-container items), but when possible, these kind of holes should be patched.

It'd be nice if I had a better time envisioning the scenario that brought about the code change, but whatever the case, a verb routine such as DoEmpty could possibly be even more robust. For instance, I can imagine some objects that should unload or empty out onto the ground and other objects that should empty out into the player's inventory. Also, possibly there should be a way to quickly disallow emptying of a container or platform without having to write up a before routine to catch it.

This all presents the question, how do we handle all of these scenarios? Right now, I'm leaning towards a global variable used with format masks or possibly just attributes. I am going to think on this. Anyone have any suggestions?

EDIT: Ok, I was just playing with this for a bit. I was trying to do some complicated grammar token stuff, but my routine grammar doesn't play nicely with the "multi" grammar token used by much of the "empty" definitions. I have come to the conclusion that probably the best route to take would be to create object classes with various DoEmpty behaviors from which other objects can inherit from.

Saturday, March 23, 2013

3.1

Just letting you all know that Roodylib v3.1 is now up on Hugo by Example and the bitbucket repository. I also uploaded it to the IF Archive, but it'll probably be some days before that one is accepted.

straight from the Hugo source

So I asked Kent Tessman about exactly what was going wrong when something like my ClearArray routine uses up all of the UNDO memory. He answered:
The way Hugo's undo currently works, it stores a stack of individual commands, and basically sticks a bookmark in where a player command would be. Sorry, but the first 'individual commands' I mean 'program statements' or 'program commands'. So when you assign a variable or -- as you discovered, a single array element -- it uses an undo slot. Now, there are lots of them, but the default (and essentially at this point standard) engine has an arbitrary limit to keep memory usage down.
So that'll be an interesting thing to keep in mind in the future. I then asked him if, memory-wise, array elements, variables, and property elements- and the setting of- are interchangeable. He answered:
For the most part.  I mean, basically what Hugo does is allocate a big chunk of bytes, and divvies it up.
 So I think the lesson to be learned from that is to not put all of your state-tracking changes in one basket but to split them up between arrays, property arrays, and variables. Whatever the case, most importantly, don't write code that sets many of these values needlessly.

Anyhow, I'll try to have a new version of Roodylib up soon.

Friday, March 22, 2013

"new fuse" progress

I really was liking this new fuse system, but the fact that using it seemed to limit even a small game to one UNDO was killing me. As far as I could tell, the problem was my call to "runevents" after an UNDO and other xverb commands. It just was taking up too much memory.

I decided just now that I'd code my own version of "runevents" that I'd run in such cases. I call it... fake_runevents!

The new "NEW_FUSE" code:
#ifclear NO_FUSES

#ifset NEW_FUSE
property fuse_length alias e_to

replace fuse
{
    type fuse
    size 0
    timer
    {
        if self.fuse_length
            return (self.fuse_length - counter)
        else
            return 0
    }
    fuse_length 0
    in_scope 0
    tick
    {
        local a
        a = self.timer
        if a <= 0
            self.fuse_length = 0
#ifset DEBUG
        if debug_flags & D_FUSES
        {
            print "[Running fuse "; number self; ":  timer = ";
            print number a; "]"
        }
#endif

        if a = 0
            Deactivate(self)
        return a
    }
}

property timer_start alias fuse_length
property fake_event alias s_to

replace daemon
{
    type daemon
    size 0
    in_scope 0
    timer
    {
        if self.timer_start
        {
            return (counter - self.timer_start)
        }
        else
            return 0
    }
}

object fuse_bucket
{}

routine fake_runevents
{
    local i
    for i in fuse_bucket
    {
!        if i.type = fuse
!        {
            run i.fake_event
!        }
    }
}
#endif  ! NEW_FUSE

!\
Activate - added a warning for when it is called before the player global has been set.
\!
replace Activate(a, set)                ! <set> is for fuses only
{
    local err
    if not player
        {
        Font(BOLD_ON)
        print "[WARNING:  The player global must be set before
        daemon (object "; number a;") can be activated.]"
        err = true
        }
#ifset NEW_FUSE
    move a to fuse_bucket
#endif
    a.in_scope = player
    a is active
    if a.type = fuse and not err
    {
        if set
#ifclear NEW_FUSE
            a.timer = set
#else
            a.fuse_length = (counter + set)
#endif

        run a.activate_event
    }
    elseif a.type = daemon and not err
    {
#ifclear NEW_FUSE
        if set and not a.#timer
#else
        if set and not a.#timer_start
#endif
        {
            Font(BOLD_ON)
            print "[WARNING:  Attempt to set nonexistent timer
                property on daemon (object "; number a; ")]"
            err = true
        }
        else
#ifclear NEW_FUSE
            a.timer = set
#else
            a.timer_start = (counter - set)
#endif
        run a.activate_event
    }
    elseif not err
    {
        Font(BOLD_ON)
        print "[WARNING:  Attempt to activate non-fuse/\
        daemon (object "; number a; ")]"
        err = true
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Activating "; a.name; " "; number a;
        if a.type = fuse
            print " (timer = "; number a.timer; ")";
        print "]"
    }
#endif
    if err
        {
        Font(BOLD_OFF)
        "\npress a key to continue..."
        HiddenPause
        }
    return (not err)
}

replace Deactivate(a)
{
    local err

    remove a
    a.in_scope = 0
    a is not active

    if a.type ~= fuse and a.type ~= daemon
    {
        print "[WARNING:  Attempt to deactivate non-fuse/\
            daemon (object "; number a; ")]"
        err = true
    }
    else
    {
        run a.deactivate_event
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Deactivating "; a.name; " "; number a; "]"
    }
#endif

    return (not err)
}
#endif ! ifclear NO_FUSES
So, you copy your "event in <fuse or daemon>" code to the fuse's fake_event property. Of course, like the old "new fuse" stuff, whether or not your code properly prints stuff will be based on whether your code uses the new counter-based timing effectively.

Anyhow, my guess is that "runevents" uses a fair amount of memory since it's doing a lot of FindObject-y stuff behind the scenes, and this method of helping Hugo find the fuses and daemons quicker seems to help my test game out a good amount.

more about what went wrong

So, let's share an example of memory-hogging code. This is the ClearArray routine that was sucking a lot of available memory out of Roodylib-using games:
routine ClearArray(array_to_be_cleared)
{
    local n,t
    for (n=0;n< array array_to_be_cleared[] ; n++ )
        {
            array array_to_be_cleared[n] = 0
        }
}

The problem was that I was using this with _temp_string every turn, and _temp_string has 256 elements, meaning that it goes through the loop 256 times. This all seems to count against the available memory.

The reason I wrote this routine in the first place was because of an instance in The Clockwork Boy 2 was because something was overwriting the end 0 bit of the current string, so the game kept printing until it got to the end of a previously-saved string. I thought, a-ha, I'll fix this by clearing _temp_string all of the time!

Of course, now I wish I had just fixed whatever was writing over that 0 bit. I can't remember what the code was like before, but in the meantime, I changed my PrintStatusLine code to not use ClearArray.

Instead of throwing ClearArray out altogether (although I might do it eventually anyway), I changed the loop to quit out as soon as the array has two empty elements in a row:

routine ClearArray(array_to_be_cleared)
{
    local n,t
    for (n=0;n< array array_to_be_cleared[] ; n++ )
        {
            if    array array_to_be_cleared[n] = 0
                t++
            else
            {
                array array_to_be_cleared[n] = 0
                t = 0
            }
            if t = 2
            {
                break
            }
        }
}

The main thing is, let this be a good lesson to not let your code loop more than it has to.

Thursday, March 21, 2013

worst case scenario

Grrr, so annoyed. While testing out that fuses code today, I noticed that games compiled with recent versions of Roodylib don't have enough memory for even one UNDO. Truth be told, I often test new code with Gargoyle since the Gargoyle window is so unobtrusive, but Gargoyle's UNDO buffer is so small that I probably didn't blink twice when newly compiled games were unable to compile.

Testing now with Hugor, Roodylib has been short on UNDO memory since before I started uploading to bitbucket (and definitely before the 3.0 release I uploaded to the IF Archive).

The good news is, getting rid of just one for loop called by PrintStatusLine freed up space for one UNDO, and compiling without my new fuse code frees up memory for even more.

The bad news is, now I need to go through all of Roodylib with a particular eye for optimization. Having games unable to UNDO is unacceptable. I've long been worried that I'd run into this problem, but it is quite annoying to have to deal with it.

My optimization strategy will consist of cutting any loops that I can and making sure that the others are as concise as possible. Beyond that, I've long been suspicious that Perform doesn't need to call SetUpDirectionObjects every time, and I think I'll see if I can move it somewhere else.

fairly fuse-ful

One of my dissatisfactions with the current state of Roodylib is that things like redrawing the screen after a screen size change or after leaving a menu does not include relevant event code if something happens to be going on. Luckily, this doesn't come up very often, and it's probably pretty easy to shrug off but it still annoys me.

You could just call runevents again, but since most fuses and daemons are timer-based, many would progress an extra turn where you do not want them to. When I used to think about how I could conceivably fix this, I thought about a complicated write-event-messages-to-string-arrays system that'd print messages again. Today, it occurred to me to change the fuse code to redefine how they work. In my new code, the timer now is a calculation between the game counter and fuse length.

While not necessary for the code to work, I added a new property to fuse objects so existing fuse code out there will still work with the new stuff. Since daemon code (I mean that in the non-fuse sense) usage is so open-ended, I took a gamble and coded it to assume the opposite-of-fuse, a timer that increases with every turn instead of decreases.

Of course, depending on what your fuse/daemon code does, you'd still have to code them strategically so they can be smartly run several times per turn.

This is another thing where I'm not sure how quickly I'll add it to RoodyLib, as it may make some assumptions that might not be good for all games.

Anyhow, the code:

property fuse_length alias e_to

replace fuse
{
    type fuse
    size 0
    timer
    {
        if self.fuse_length
            return (self.fuse_length - counter)
        else
            return 0
    }
    fuse_length 0
    in_scope 0
    tick
    {
            if self.timer <= 0
                self.fuse_length = 0
#ifset DEBUG
        if debug_flags & D_FUSES
        {
            print "[Running fuse "; number self; ":  timer = ";
            print number self.timer; "]"
        }
#endif

        if self.timer = 0
            Deactivate(self)
        return self.timer
    }
}

property timer_start alias fuse_length

replace daemon  ! replaced just the daemon is near the fuse in the object tree
{
    type daemon
    size 0
    in_scope 0
    timer
    {
        if self.timer_start
        {
            return (counter - self.timer_start)
        }
        else
            return 0
    }
}


!\
Activate - added a warning for when it is called before the player global has been set.
\!
replace Activate(a, set)                ! <set> is for fuses only
{
    local err
    if not player
        {
        Font(BOLD_ON)
        print "[WARNING:  The player global must be set before
        daemon (object "; number a;") can be activated.]"
        err = true
        }
    a.in_scope = player
    a is active
    if a.type = fuse and not err
    {
        if set
            a.fuse_length = (counter + set)

        run a.activate_event
    }
    elseif a.type = daemon and not err
    {
        if set and not a.#timer_start
        {
            Font(BOLD_ON)
            print "[WARNING:  Attempt to set nonexistent timer
                property on daemon (object "; number a; ")]"
            err = true
        }
        else
            a.timer_start = (counter - set)

        run a.activate_event
    }
    elseif not err
    {
        Font(BOLD_ON)
        print "[WARNING:  Attempt to activate non-fuse/\
        daemon (object "; number a; ")]"
        err = true
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Activating "; a.name; " "; number a;
        if a.type = fuse
            print " (timer = "; number a.timer; ")";
        print "]"
    }
#endif
    if err
        {
        Font(BOLD_OFF)
        "\npress a key to continue..."
        HiddenPause
        }
    return (not err)
}

replace Deactivate(a)
{
    local err

    remove a
    a.in_scope = 0
    a is not active

    if a.type ~= fuse and a.type ~= daemon
    {
        print "[WARNING:  Attempt to deactivate non-fuse/\
            daemon (object "; number a; ")]"
        err = true
    }
    else
    {
        run a.deactivate_event
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Deactivating "; a.name; " "; number a; "]"
    }
#endif

    return (not err)
}
#endif ! ifclear NO_FUSES

Wednesday, March 20, 2013

Describe-re-Place

I have to admit it. I love "to do" lists. This is one of the reasons I'll miss iGoogle when it's gone, as it provides a "to do" list app for the unobtrusive Chrome home page I have set up. That list is great, but I also have to admit that some of my most effective "to do" lists are just a couple text files I have on my desktop.

I've been revisiting those desktop lists lately to make sure I've accomplished everything listed there. I was able to delete a good deal of what they listed, but at least one of them reminded me of an unsuccessful bid to redesign DescribePlace into something that'd allow the addition of extra bits of text, like something that'd call that CanGo routine that lists room exits.

I like running DescribePlace with the DESCFORM_F format mask which gives room contents a sort of "double spaced" look to it. The heart of the DescribePlace problem is that you really need to know when stuff will be printed and when it will not to properly space it all.

Even though I really had no intention to return to the problem any time soon, I happened to lie down for a late afternoon nap yesterday, and almost instantly, I was hit by a DescribePlace replacement idea. Instead of having it be one routine, I thought I'd give it a main routine that calls routines that does the actual object listing. Then we have the benefit of returning true or false to determine whether or not to print extra lines.

I spent some time today putting the idea to action, and it already seems to be in a usable state. I think I even fixed some problems with the old replacement which required some elements to be listed after certain other ones. I have a feeling some of the routines could be more efficient or cleaner-looking, but that's for another day.

What does this mean for the average author? Well, since they probably won't touch anything, it doesn't really mean anything. Everything should work the same, although there are also some fixes in there for when using-DescribePlace-to-describe-a-room-that-isn't-your-location.

Like the old replacement, it should be helpful for games that like to change up the order in which things are listed, like Robb's games where characters are sometimes listed last. To change the order, just add something like this to your init or SetGlobalsAndFillArrays routine:
    DescribePlaceArray[0] = &CharsWithoutDescs, &ObjsWithoutDescs,
    &ParentofPlayer, &CharsWithDescs, &ObjsWithDescs, &AttachablesScenery
 (Except in whatever order you want)

Being so modular, it should also be easier to change the behavior of just how certain things are listed.

I also think that this will added to Roodylib, but I have to first decide if all of the new spacing behavior should fall under DESCFORM_F or if I need to come up with a new Infocom-spacing format mask called DESCFORM_I or something. I'll have to look at my old notes to see what I decided with that.

Anyhow, here's the code so far:
! This constant needs to be declared with something higher if you are
! adding additional elements into the mix
#if undefined DESCRIBEPLACE_ELEMENTS
    constant DESCRIBEPLACE_ELEMENTS 6
#endif

attribute already_printed
array DescribePlaceArray[DESCRIBEPLACE_ELEMENTS]

replace Describeplace(place, long)
{
    local obj,  didprint
    local need_carriage

    parser_data[PARSER_STATUS] &= ~PRONOUNS_SET

    ! Since, for example, a room description following entering via
    ! DoGo does not trigger before/after properties tied to looking
    ! around:
    !
#ifclear NO_VERBS
    if verbroutine = &MovePlayer ! was "if verbroutine ~= &DoLookAround"
    {
        if place is not visited and verbosity ~= 1
            return Perform(&DoLookAround)
        elseif long = true or verbosity = 2
            return Perform(&DoLookAround)
    }
#endif

    if not light_source
    {
        Message(&DescribePlace, 1)     ! "It's too dark to see..."
        return
    }

    place is known

    ! Print the name of the location as a heading
    Font(BOLD_ON)
    if not (FORMAT & DESCFORM_F)
        print "\n";

    print capital place.name;

    ! Print ", in <something>" if necessary
    if location = place and player not in place
    {
        if parent(player).prep
            print ", "; parent(player).prep; " ";
        elseif parent(player) is platform
            print ", "; ON_WORD; " ";
        else
            print ", "; IN_WORD; " ";
        print Art(parent(player))
    }
    print newline

    Font(BOLD_OFF)
    override_indent = false

    if place is not visited and verbosity ~= 1
    {
        if &place.initial_desc or &place.long_desc
            {
            didprint = true
            Indent
            }
        if not place.initial_desc
            run place.long_desc
    }
    elseif long = true or verbosity = 2
    {
        if &place.long_desc
            {
            Indent
            didprint = true
            }
        run place.long_desc
    }
    elseif place is not visited and verbosity = 1
    {
        if &place.initial_desc
            {
            Indent
            didprint = true
            }
        run place.initial_desc
    }

    if &place.list_contents and (FORMAT & DESCFORM_F) and didprint
        print ""        ! for double-space-after-heading formatting

    ! A location may contain an overriding listing routine, as may any
    ! parent, in the list_contents property
    !
    for obj in place
        obj is not already_printed

    if not place.list_contents
    {
        list_nest = 0
        local a
        while a < DescribePlaceArray[] and DescribePlaceArray[a]
        {
            need_carriage = call DescribePlaceArray[a](place)
            if (FORMAT & DESCFORM_F)
            {
                if need_carriage and didprint
                    ""
            }

            if need_carriage
            {
                need_carriage = false
                didprint = call DescribePlaceArray[a](place,true)
            }
            a++
        }
        print newline
        need_newline = false
    }
}

! routine for listing the player's siblings when he or she is in or on an
! object in the room
routine ParentofPlayer(place, for_reals)
{
    local obj
    if not for_reals
    {
        if player not in place and place = location
        {
            for obj in (Parent(player))
            {
                if obj is not hidden and obj ~= player
                    return true
            }
        }
        return false
    }
    else
    {
        list_nest = 1
        if WhatsIn(Parent(player))
            return true
    }
}

! routine for listing characters with short_desc descriptions
routine CharsWithDescs(place, for_reals)
{
    local obj
    if not for_reals
    {
        for obj in place
        {
            if (obj is not hidden and obj is living and
                 obj ~= player and ((&obj.short_desc and verbosity ~= 1) or
                (obj is not moved and &obj.initial_desc)) and
                obj is not already_printed )
            {
                return true
            }
        }
        return false
    }
    else
    {
        ! List all characters, if any
!        count = 0
        for obj in place
        {
            if obj is hidden or obj is not living or
                player in obj
            {
                obj is already_listed
            }
            else
            {
                obj is not already_listed
            }
        }
        local ret
        for obj in place
        {
            if obj is not already_listed
            {
                print newline
                if verbosity ~= 1 or (obj is not moved and &obj.initial_desc)
                    ShortDescribe(obj)
                if obj is already_listed  ! did we print something?
                {
                    ret = true
                    obj is already_printed
                }
            }
        }
        return ret
    }
}

! routine for listing characters without short_desc descriptions
routine CharsWithoutDescs(place,for_reals)
{
    local tempformat, count, obj
    if not for_reals
    {
        for obj in place
        {
            if (obj is not hidden and obj is living and
            obj ~= player and
            (not &obj.short_desc or
            (&obj.short_desc  and verbosity = 1)) and
            obj is not already_printed)
            {
                return true
            }
        }
        return false
    }
    else
    {
        for obj in place
        {
            if (obj is not hidden and obj is living and
            obj ~= player and
            (not &obj.short_desc or
            (&obj.short_desc  and verbosity = 1)) and
            obj is not already_printed)
            {
                obj is not already_listed
                count++
            }
            else
                obj is already_listed
        }
        if count ! list_count      ! if characters are to be listed
        {
            list_count = count
            tempformat = FORMAT
            FORMAT = FORMAT | FIRSTCAPITAL_F | ISORAREHERE_F
            FORMAT = FORMAT | USECHARNAMES_F
            if (FORMAT & LIST_F)
            {
                FORMAT = FORMAT & ~LIST_F       ! clear it
                FORMAT = FORMAT | TEMPLIST_F
            }
            Indent
            list_nest = 0
            ListObjects(place)
            FORMAT = tempformat
            return true
        }
        return false
    }
}

! routine for listing objects with short_desc descriptions
routine ObjsWithDescs(place, for_reals)
{
    local obj, ret
    if not for_reals
    {
        for obj in place
        {
    #ifset USE_ATTACHABLES
        ! Exclude all attachables for now (and characters)
            if obj is not living and not obj.type = attachable and
                player not in obj and obj is not hidden and
                ((verbosity ~= 1 and &obj.short_desc) or
                (&obj.initial_desc and verbosity = 1)) and
                obj is not already_printed
    #else
            if obj is not living and player not in obj and
            obj is not hidden and
            ((verbosity ~= 1 and &obj.short_desc) or
                (&obj.initial_desc and verbosity = 1)) and
                obj is not already_printed
    #endif
            {
                ret = true
                obj is not already_listed
            }
            else
                obj is already_listed
        }
        return ret
    }
    else
    {
        for obj in place
        {
#ifset USE_PLURAL_OBJECTS
                    ! ...And don't list identical objects yet, either

            if (obj.identical_to).type = identical_class
            {
                if obj is not hidden
                    count++
            }
            elseif player not in obj
#else
            if player not in obj
#endif
            {
                if obj is not already_listed and
                    obj is not hidden and obj is not already_printed
                {
                    if verbosity ~= 1 or (verbosity = 1 and
                        (obj is not moved and &obj.initial_desc))
                        ShortDescribe(obj)
                    if obj is already_listed ! did we print something?
                    {
                        ret = true
                        obj is already_printed
                    }
                }
            }
        }
        return ret
    }
}

! routine for listing objects without short_desc descriptions
routine ObjsWithoutDescs(place, for_reals)
{
    local obj, tempformat, count
    if not for_reals
    {
        for obj in place
        {
#ifset USE_ATTACHABLES
            ! Exclude all attachables for now (and characters)

            if obj is not living and not obj.type = attachable and
                player not in obj and obj is not hidden and
                obj is not already_printed and
                (not &obj.short_desc or (&obj.short_desc and verbosity = 1))
#else
            if obj is not living and player not in obj and
            obj is not hidden and obj is not already_printed and
            (not &obj.short_desc or (&obj.short_desc and verbosity = 1))
#endif
                {
                return true
                }
        }
        return false
    }
    else
    {
        for obj in place
        {
#ifset USE_ATTACHABLES
            ! Exclude all attachables for now (and characters)

            if obj is living or obj.type = attachable or
                player in obj or (&obj.short_desc and verbosity ~= 1) or
                obj is already_printed
#else
            if obj is living or player in obj or (&obj.short_desc and verbosity ~= 1) or obj is already_printed
#endif
                obj is already_listed
            else
            {
                count++
                obj is not already_listed
            }
        }

        if count
        {
            list_count = count
            tempformat = FORMAT
            FORMAT = FORMAT | FIRSTCAPITAL_F | ISORAREHERE_F
            if FORMAT & LIST_F
            {
                FORMAT = FORMAT & ~LIST_F       ! clear it
                FORMAT = FORMAT | TEMPLIST_F
            }
            Indent
            list_nest = 0
            ListObjects(place)
            FORMAT = tempformat
            return true
        }
        return false
    }
}

! routine for listing attachables and contents of scenery objects
routine AttachablesScenery(place, for_reals)
{
    local obj, ret
    if not for_reals
    {
#ifset USE_ATTACHABLES
        for obj in place
        {
            ! Print attachables last
            if obj.type = attachable and obj is not hidden
            {
                return true
            }
        }
#endif
        for obj in place
        {
            if obj.type = scenery
            {
                if player not in obj and
                    (obj is open or obj is not openable)
                {
                    local a
                    for a in obj
                        {
                        if a is not hidden
                            {
                            return true
                            }
                        }
                }
            }
        }
        return false
    }
    else
    {
#ifclear NO_OBJLIB
#ifset USE_ATTACHABLES
        for obj in place
        {
            ! Print attachables last
            if obj.type = attachable and obj is not hidden
            {
                ShortDescribe(obj)
                if obj is not already_listed
                    Message(&DescribePlace, 2, obj)
                obj = already_printed
                ret = true
            }
        }
#endif
        print newline
        override_indent = false

        ! Finally, list contents of scenery objects (unless we've
        ! already done so as the parent of the player)
        !
        for obj in place
        {
            if obj.type = scenery
            {
                obj is known
                if player not in obj and
                    (obj is open or obj is not openable)
                {
                    list_nest = 1
                    if WhatsIn(obj)
                        ret = true
                }
            }

            ! For scenery-derived objects that may change the type
            elseif obj is static, hidden
                obj is known
        }
        return ret
#endif  ! ifclear NO_OBJLIB
    }
}

Thursday, March 14, 2013

simpletalk

I did upload Roodylib the other day. While I was at it, I uploaded my latest versions versions of beta.h, newmenu.h, and simpletalk.h. Of course, once you upload something, you think of how something could be further improved, and I decided I'd finally add looping-conversation-menu functionality to simpletalk and newsimpletalk.

For whatever reason, in my mind's eye, I thought it'd be something as simple as:
    while true
    {
        if not Phototalk
            break
    }
But the reality was that my code wasn't setup for such a thing. So I added some stuff and long story short, setting the new loop_talk global variable to true causes conversation menu to loop for as long as options are available or until the player quits.

 After that, I added a can_quit global variable that defaults to true, but if set to false, the player doesn't have an option for leaving the conversation menu.

The new versions are up at Hugo by Example but I forgot to mention the new features in the comment documentation. Oh well, next release.

Monday, March 11, 2013

what now?

With my last Roodylib update, I hit version 3.0. Admittedly, my version numbering is nothing more than adding a .1 with every release. Still, the next things on my Roodylib to-do list are inconsequential enough that I may make use of the clean version number and finally upload a version of Roodylib to the IF Archive.

One of the things I'm debating adding to Roodylib in the future is some replacement of DescribePlace. The problem is, I think that my "does everything" version of DescribePlace is kind of an eyesore so I'd like to either clean it up or put a somewhat less feature-ful version in Roodylib. The recent configuration-file-writing stuff could be cleaner, too.

As far as new extensions go, the remaining ideas are ones that I'm comfortable putting on the backburner for a while, so I really have no excuse to not dig big into my game ideas. Just the same, I'd like to mention them here right now as a bit of a teaser.

The first extension idea is a "Scott Adams game" extension, which would hopefully make it easy to hide a Scott Adams-type game in another game. Basically, I thought it'd be hilarious if multimedia-using games opened up as Scott Adams type games in multimedia-incapable terps.

There have been a couple difficulties with this. The first was figuring out a way to make the Scott Adams game grammar coexist with the regular game grammar. I'm pretty sure I've come up with a good solution for this.

The second one is a bit harder. Somewhere along the way, I decided that I wanted to copy actual-Scott-Adams-game behavior by putting all of the non-prompt game text in the status window (which would take up the top half the screen). I think that last time I worked on the extension, I began a string saving system to handle this, but I haven't gotten the extension into a compilable state yet. Part of the difficulty has been trying to decide if default messages should ever be anything other than "OK!" and "I CAN'T DO THAT!" or whatever.

Anyhow, as much as the idea behind the extension amuses me, I'm not currently working on any multimedia games that would use it, and I am likely the only person who would use such an extension.

My other extension idea is a "beginner interface" that could be popped into most games that'd show basic information that player's could click to add to the command line. This idea grew out of the idea that most plain text Hugo games played in a fullscreen or near-fullscreen window have more space than they know what to do with (at least if you aren't using Hugor's cool margin size feature). The general layout of the interface would look like this:
There are several things that have kept me from writing this one, too. For one thing, I've been avoiding the headache of writing the code that figures how many windows are available with the given space. Something like the inventory window would be especially troublesome, where it could conceivably be a long list comprised of long names.

I also had this idea before I did the "default menu" for newmenu.h. That provides a page with common commands, and doing that took some of the wind out of my sails for this project.

Lastly, I think this design demands the use of at least three colors, and I don't even want to think about how I'm going to determine that third color.

So, yeah, there are those things. Hopefully, the next time you hear from me, I'll be knee deep in game code.

Saturday, March 9, 2013

configuration tribulations

So, when I was finishing up "The Next Day," it was irking me that my game's usage of my cover art and colorlib extensions and music options meant that the game was creating 2 or 3 data files. In Hugor, this is very obvious as the files appear in the game directory *.

*  Nikos asked whether this should possibly be changed so that it emulates the regular interpreter behavior, but I thought it was good as it is. I think the player *should* have some idea what's going on, especially in games where deleting the data file is the only way to get a new clean start. Still, having the two behaviors among the two interpreters makes it harder for us authors to code for.

For "The Next Day," I ended up just replacing routines and rewriting them to account for every extension, but I promised myself I'd write some kind of configuration file manager extension that'd automatically write all of these different extensions to one file.

It took several weeks and a couple different approaches. Initially, I was trying to write the manager based on the existing extensions; that resulted in this unsuccessful system where I was using a big array to temporarily hold stuff but I couldn't keep track of where things were after a while. When I started the design from the "manager-side" of things, it was a lot simpler to design. The hard part was still adapting the old extensions to the new system, but I'm mostly done with that already, I think.

Part of the problem with that last thing is that I'm especially dissatisfied with the current state of the cover art extension. Looking at it pragmatically, some of its features shouldn't even exist. I created the thing to emulate the zblorb cover art system. There's a chance the "cover once" (show a game's cover just once) feature would be worthwhile if everyone used the official Hugo interpreter, which writes all data files to the same directory, but Hugor is just way too nice of a interpreter to act like it doesn't exist. I just recently was getting Hugo to work on Puppy Linux, and Hugor was the only one I could easily set-up (and hey, it looked really nice, too). Anyway, having all of those cover art choices in such an environment is nonsensical.

There's also the argument that Hugo games shouldn't even have any cover art at all as it can't be read by an external program (and cover art exists in the first place for the benefit of game loaders), but I dunno, I think cover art looked cool in Paul Lee's Tree & Star so I think the extension is worth keeping around.

Side thought: as far as the last sentiment is concerned, I imagine it'd very possible to have a game loader allow you to point to a Hugo's resource file, at which it could either grab the first graphic (which would likely be the title) or browse through the pictures to choose something to use as cover art. Anyhow, I guess it's something to suggest if game loaders ever really do come far enough.

My current thoughts on how the cover art extension should behave is this:
* Cover art defaults to on and shows up every time the game is opened up cleanly
* It does not show during game restarts, though.
* It can still be turned off.

Also, I'm thinking of calling this configuration file manager "config_sys.h". Funny? Or confusing?

Do people have thoughts about any of these things?

EDIT: Dang it, I forgot to mention some of the cool things my configuration file manager does that I am especially proud of, so here goes:

Especially when I was using that big temporary array to hold the configuration file info, one of my big problems was how to identify the start of this extension's info and that one's. Eventually, I came up with a routine I called "StringSum". I'd write the extension's configuration file's name to a string then add up all of the character values. I'd then write this value to the array before saving its info, so i could check for this value later. While it's entirely feasible that multiple extensions might have the same name sums, it was a semi-reasonable way to distinguish one library's info's start from the other.

In the second iteration of the configuration file manager, I also used object hierarchy as an ordering tool, so all of the configuration objects are in a config_instructions object, and I go through its children one by one. I kept the StringSum system, so between the StringSum-ing and the object hierarchy, the system should have a good grasp of what's what.

Wednesday, March 6, 2013

coding style

I only took 1.5 coding classes in college, and I don't remember them going into great length about code readability, other than it being a good idea. I don't even remember code commenting being very important (of course, our programs were really short and simple).

I find that as I code in Hugo, I am adopting Kent's coding style more and more. I mean, for a long time, I've understood the importance of indentation, but even getting that right has been a bit tricky. My old code was probably over-indented, but now I've picked up one more rule from Kent's style:

Brackets are not indented

If you think about it, the following object definition:
 object
{}
 already establishes that brackets aren't indented, so not indenting with them is nice from a consistency viewpoint.

Now, whatever is in the bracket is indented, of course. Also,

Brackets get their own line

The other thing I do for readability these days is always give brackets their own line. This makes it easier to keep things aligned.

Now, of course, both of these things are up to personal taste, but I thought I'd share my reasonings for other Hugo authors who haven't put much thought into it.