Saturday, August 18, 2012

>GO TO Hugo

Turns out my newmenu extension still has some annoying deficiencies. In the thought-I-fixed-that-but-if-so-I-lost-the-updated-code department, it turns out that my latest version wasn't clearing the screen enough when menus are initially opened or when options are selected. Fixing it ended up being a bit more complicated than I figured- and I'm not yet sure I've found my optimal solution- because, depending on the interpreter and the speed of the computer, clearing the screen (or "window 0") can cause text flicker which just annoys me for whatever reason. I'm trying to optimize it so only necessary text is cleared, allowing for as little flicker as possible.

Two of my WIPs are either entirely or partially driven by a >GO TO [room] mobility system. The second one, which I just recently started working on, got me thinking that I should turn the idea into an extension (or at least a page on Hugo by Example). I haven't really perfected anything, but I'll share what I've got so far.

First off, before "hugolib.g" is included, I declare some new >GO grammar:

 verb "go", "walk"
    *                            DoGo
    * "to"/"in"/"into"/"inside"/"through" anything            DoGoTo
    * "out" object                                            DoGo
    * "out"/"outside"                                         DoExit
    * anything                                                DoGoTo

Now, since we're going to be giving rooms noun and adjective properties, making them refer-able, we'll replace the room class with something that redirects a lot of actions:

replace room
{
    type room
    is static, light, open, known
    before
        {
        object
            {
            if verbroutine = &DoGo, &MovePlayer,&DoLook, &DoGoTo
                {
                return false
                }
            object = 0
            Perform(verbroutine)
            return true
            }
        object DoLook
            {
            Perform(&DoLookAround)
            }
        }
}
EDIT: It turns out the above is really bad code, as lots of verb routines (like DoGet) expect to have an object value, so Perform-ing them with no object actually makes you pick up the nothing object (and so on). Here is some better code (although it's kind of "blunt force"; maybe there's a more elegant solution):
replace room
{
    type room
    is static, light, open, known
    before
        {
        object
            {
                if verbroutine = &DoMove,&DoGet, &DoDrop, &DoPutIn,
                &DoEmptyOrGet, &DoGive, &DoShow, &DoWear, &DoTakeOff,
                &DoEat, &DoDrink, &DoHit, &DoLookThrough, &DoLookUnder, &DoExit
                    {
                    "I didn't understand you. Try being more specific."
                    return true
                    }
                elseif verbroutine = &MovePlayer,&DoLook, &DoGoTo
                {
                    return false
                    }
                elseif verbroutine = &DoGo, &DoEnter
                    {
                    Perform(&DoGoto, self)
                    return true
                    }
            object = 0
            Perform(verbroutine)
            return true
            }
        object DoLook
            {
            Perform(&DoLookAround)
            }
        }
}
We'll also make an "exits" property that keeps track of which rooms are available from other rooms (in this game, >GO TO just works room-to-room; this is not intended to be a Magnetic Scrolls type >GO TO scenario).

property exits
Now, our DoGoTo routine:
routine DoGoTo
{
    if object = location
        {
        "You're already there!"
        return false
        }
    if not FindObject(object, location)
        {
        if object.type = room
            {
            if InList(location,exits,object)
                {
                return MovePlayer(object)
                }
            else
                {
                if not location.cant_go
                    {
                    "You can't go that way."
                    }
                return false
                }
            }
        }
        return Perform(&DoGo,object)
}
A "here" object to redirect things like >GO TO HERE to the current location:
object here_object "here"
{
    noun "here"
    before
        {
        object
            {
            Perform(verbroutine, location)
            }
        xobject
            {
            Perform(verbroutine, object, location)
            }
        }
    in_scope you
    is known
}
An example room:
room STARTLOCATION "Start Location"
{
    adjective "start"
    noun "location"
    exits pond, barn
}

 So, that's basically it. An issue: one of the things my code does is make every room known so that MovePlayer works. If you still want a sense of discovery in your games, you could have DoGoTo automatically make rooms listed in the exits property known (so that rooms 2 connections away might still be a mystery).

Anyhow, I don't think the code is ready for HbE yet, but I thought people might find it interesting just the same. Plus, hey, this blog needs all of the posts it can get.

2 comments:

  1. I'm sure your code is much more solid than mine could ever be, but since you don't feel that your code is completely ready yet, I'll post my own approach, just in case it may give you any ideas.

    The code is based on scenery objects representing the locations that can be visited from another room. For example, the psuedo_kitchen object has the noun "kitchen", and its found_in property puts it in in the two rooms from which the player can enter the kitchen, as well as in the kitchen room itself, just to catch the case of someone trying to "ENTER KITCHEN" from the kitchen. (This code is based on the command ENTER rather than GO TO, but I think this method could be implemented with GO TO.)

    scenery pseudo_kitchen "kitchen"
    {
    noun "kitchen"
    article "the"
    found_in foyer, kitchen, living_room
    before
    {
    object DoEnter
    {
    FindHouseDir(kitchen)
    }
    }
    }


    And here is the routine that is called whenever the player tries to enter a "pseudo" object:

    routine FindHouseDir(entering)
    {
    if entering = location
    {
    "You're already in ";
    select entering
    case kitchen
    {
    print The(pseudo_kitchen); "."
    }
    case foyer
    {
    print The(pseudo_foyer); "."
    }
    case living_room
    {
    print The(pseudo_living_room); "."
    }
    }
    else
    {
    select location
    case foyer
    {
    if entering = kitchen
    {
    Perform(&DoGo, e_obj)
    }
    elseif entering = living_room
    {
    Perform(&DoGo, n_obj)
    }
    }
    case kitchen
    {
    if entering = foyer
    {
    Perform(&DoGo, w_obj)
    }
    elseif entering = living_room
    {
    Perform(&DoGo, nw_obj)
    }
    }
    case living_room
    {
    if entering = foyer
    {
    Perform(&DoGo, s_obj)
    }
    elseif entering = kitchen
    {
    Perform(&DoGo, se_obj)
    }
    }
    }
    }


    My method was only based on a specific case, and I'm not sure if it can easily be made more generic. I'll just throw it out there just on the off chance that any of my ideas might work well within your solution. :)

    ReplyDelete
    Replies
    1. Thanks for contributing. Throwing one's own hat into the ring is always a good way to get to know Hugo better. Plus, it's always good to be reminded of the different ways one can approach a problem.

      I only said my code wasn't ready for primetime since I felt like I hadn't adequately barraged it with problematic scenarios. That said, I think it's a good start.

      Your found_in method is also a viable way to do it. Personally, I would lessen how much code I repeat as much as possible by creating a class object (even if the only identical code between the various "fake rooms" would be their before properties). In FindHouse, I'd also probably save myself the trouble of entering all of the various directions (Perform(&DoGo,se_obj)) and just call MovePlayer directly (MovePlayer(kitchen)).

      I'd probably also bulk up the before property to make sure it has adequate responses to more commands.

      That all said, yeah, that's a fine way to do it, especially for a smaller game where one doesn't mind entering all of the specific info.

      Delete