Monday, September 17, 2012

What's my PrintStatusLine?

I've been pretty good lately about coming up with roodylib improvements instead of working on my game. The last idea I had was especially challenging, and I have to admit, it took a couple days of pecking to truly get up the steam to finish it.

In a nutshell..

I've been annoyed that some of my library extensions have to replace PrintStatusLine. What do you do when two of your extensions replace PrintStatusLine? Of course, you hack together a version that works this way in some circumstances and that way in others. Like other parts of roodylib, I wondered if there wasn't a way to make it more modular.

Now, I probably could have done it all with multiple routines and maybe some global variables and arrays, but like my other "settings" in roodylib, I decided to make it object based. Extension PrintStatusLine objects have properties that point to routines that determine the height they need and instructions for drawing their portion of the status window.

Even when you aren't using fancy library extension stuff, you could make the status window bigger than usual and draw whatever you want in the extra space (and you can make the normal status part of the window "bottom justified" if you want).

I just finished this thing so hopefully I'll be able to pretty it up a little so it's easier to understand at some point, but it's uncertain that this version of PrintStatusLine will be readable to anyone but me, unfortunately. Let's take a look at what I wrote:
property find_height alias u_to
property draw_window alias e_to
property bottom_justified alias d_to
property terp_type alias w_to

! "terp_type" values  0, 2, 4
enumerate step * 2
{
    NORMAL_TERP, GLK_TERP = 2, SIMPLE_TERP
}

object printstatuslib
{
    find_height
        {
        local sum, i
        for i in self
            {
            sum += i.find_height
            }
        return sum
        }
    draw_window
        {
        local i
        for i in self
            {
            run i.draw_window
            }
        }
    terp_type NORMAL_TERP
    bottom_justified 0
}

! this object and its properties draw the status window as we normally know it
object statuswindow
{
    in printstatuslib
    find_height
        {
        return (call &FindStatusHeight)
        }
    draw_window
        {
        return (call &WriteStatus)
        }
}

replace PrintStatusline
{
local newstatusheight
#ifset CHEAP
    if cheap
        return
#endif

! set the "terp_type" value
if IsGlk
    printstatuslib.terp_type = GLK_TERP
elseif system(61) ! minimal port
        printstatuslib.terp_type = SIMPLE_TERP
else
    printstatuslib.terp_type = NORMAL_TERP

! figure out the size our window will be
newstatusheight = printstatuslib.find_height

! remove the windows if the status window height has changed
if (newstatusheight < display.statusline_height) and not system(61)
    window 0

display.statusline_height = newstatusheight

Font(BOLD_OFF | ITALIC_OFF | UNDERLINE_OFF | PROP_OFF)
window display.statusline_height
    {
    color SL_TEXTCOLOR, SL_BGCOLOR
    if printstatuslib.terp_type ~= SIMPLE_TERP
        {
        cls
        locate 1,1
        }
    run printstatuslib.draw_window
    }
color TEXTCOLOR, BGCOLOR, INPUTCOLOR
Font(DEFAULT_FONT)
}

! routine for finding the height of the regular status info
replace FindStatusHeight
{
local a, b
text to _temp_string
if not location
    print "\_";
elseif not light_source
    print "In the dark";
else
    {
        if FORMAT & DESCFORM_F:  print "\_";
        print capital location.name;
        print "\_";
    }
text to 0
a = StringLength(_temp_string)

if STATUSTYPE = 1
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F)
        print "\_";
    print number score; " / "; number counter;
    if (FORMAT & DESCFORM_F)
        print "\_";
    text to 0
    }
elseif STATUSTYPE = 3
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    print "Score: "; number score; "\_ "; "Moves: "; number counter;
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    b = StringLength(_temp_string)
    }
elseif STATUSTYPE = 2
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    print HoursMinutes(counter);
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    }
elseif STATUSTYPE = 4
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    STATUSTYPE4 ! routine for configurable statusline
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    }

if (b + a + 4)<display.screenwidth ! let's force a 4 character gap between
    {                               ! the two fields
    return 1
    }
elseif (b + a - 4 ) < display.screenwidth and STATUSTYPE = 3
    {
    text to _temp_string
    if (FORMAT & DESCFORM_F) : print "\_";
    print "S: "; number score; "\_ "; "M: "; number counter;
    if (FORMAT & DESCFORM_F) : print "\_";
    text to 0
    return 1
    }
else
    return 2
}

! Roody's note: Replace this if you want to use the top right area
! for something else ("HUNGRY", "TIRED", or whatever)
replace STATUSTYPE4
{}

! routine for drawing the regular status
routine WriteStatus
{
    if printstatuslib.bottom_justified and
        printstatuslib.terp_type ~= SIMPLE_TERP
        {
        if statuswindow.find_height = 2
            {
            locate 1, (display.windowlines - 1)
            }
        else
            locate 1, display.windowlines
        }
    if not location
        print "\_";
    elseif not light_source
        print "In the dark";
    else
        {
            if FORMAT & DESCFORM_F:  print "\_";
            print capital location.name;
        }

    if statuswindow.find_height = 1 and STATUSTYPE
        {
            print to (display.screenwidth - \
            (StringLength(_temp_string) + \
            ((printstatuslib.terp_type = SIMPLE_TERP)*2)));
            StringPrint(_temp_string)
        }
    elseif STATUSTYPE and statuswindow.find_height = 2
        {
            if printstatuslib.terp_type ~= SIMPLE_TERP and
            not printstatuslib.bottom_justified
                locate 1, 2
            else
                ""
            StringPrint(_temp_string)
        }
}

So, say, if you're using newconverse's version of this (after I update it, that is), it'd point printstatuslib find_height and draw_window properties to either these routines or newconverse's status window instructions, depending on whether the player is in a conversation.

 Hopefully, this isn't too complicated.

One other thing...

I also threw together an extension for score notification, as I never have these things around when I need them. I'll add proper documentation and upload it somewhere at some point:

!::
! Hugo Score Notification extension
!::

!\
Provides text like "You score has gone up by [x] points!"
\!

#ifclear _SCORENOTIFY_H
#SET _SCORENOTIFY_H

#ifset VERSIONS
#message "ScoreNotify.h Version 0.5"
#endif

property score_notify alias d_to

property points alias e_to

object scorenotifylib "scorenotify"
{
    score_notify true
    points 0
#ifset _ROODYLIB_H
    save_info
        {
        select self.score_notify
            case 0 : SaveWordSetting("score_off")
            case 1 : SaveWordSetting("score_on")
        return true
        }
    type settings
    in init_instructions
    execute
        {
        local a
        a = CheckWordSetting("scorenotify")
        if a
            {
            select word[(a-1)]
                case "score_off": self.score_notify = 0
                case "score_on": self.score_notify = 1
            }
        }
#endif
#ifset _NEWMENU_H
    usage_desc
        {
        "\BSCORE NOTIFICATION ON\b- Be notified when you score points."
        Indent
        "\BSCORE NOTIFICATION OFF\b- Play without score notifications."
        }
#endif ! NEWMENU
}

#ifset _ROODYLIB_H
object scorenotifymain
{
    type settings
    in main_instructions
    execute
        {
        ScoreNotify
        }
}
#endif  ! _ROODYLIB_H

routine ScoreNotify
{
      if scorenotifylib.points and scorenotifylib.score_notify
           {
              ""
              Font(BOLD_ON)
           ScoreNotificationMessage(&ScoreNotify, 1, scorenotifylib.points ) ! "[Your score has gone up.]"
              Font(BOLD_OFF
           }
      score += scorenotifylib.points   ! add the points to the score
      scorenotifylib.points = 0    ! reset the point counter
}

! routine to call for the last score of a game (after the winning move), as
! main is not called again
routine LastScore(a)
{
    score += a
}

! otherwise, call this routine to add to the game score
routine AddScore(a)
{
    scorenotifylib.points += a
}

routine DoNotifyOnOff
{
      if scorenotifylib.score_notify
            Perform(&DoNotifyOff)
      else
            Perform(&DoNotifyOn)
}

routine DoNotifyOn
{
      if scorenotifylib.score_notify
           ScoreNotificationMessage(&DoNotifyOn, 1 ) ! "[Score notification already on.]"
      else
           {
           ScoreNotificationMessage(&DoNotifyOn, 2 ) ! "[Score notification on.]"
           scorenotifylib.score_notify = 1
           }
}

routine DoNotifyOff
{
      if not scorenotifylib.score_notify
           ScoreNotificationMessage(&DoNotifyOff, 1 ) ! "[Score notification already off.]"
      else
           {
           ScoreNotificationMessage(&DoNotifyOff, 2 ) ! "[Score notification off.]"
           scorenotifylib.score_notify = 0
           }
}

routine ScoreNotificationMessage(r, num, a, b)
{
    if NewScoreNotificationMessages(r, num, a, b):  return

    select r
        case &DoNotifyOn
            {
            select num
                case 1:  "[Score notification already on.]"
                case 2: "[Score notification on.]"
            }
        case &DoNotifyOff
            {
            select num
                case 1:  "[Score notification already off.]"
                case 2: "[Score notification off.]"
            }
        case &ScoreNotify
            {
            select num
                case 1 : "[Your score has gone up.]"
            }
}

!\ The NewScoreNotificationMessages routine may be REPLACED and should return
true if a replacement message exists for routine <r> \!

routine NewScoreNotificationMessages(r, num, a, b)
{
   select r
!    case &ScoreNotify
!        {
!        select num
!            case 1
!                {
!                print "[Your score has gone up by "; number a; " points.]"
!                }
!        }
   case else : return false
   return true ! this line is only reached if we replaced something
}

#endif _SCORENOTIFY_H
Actually, I haven't even compiled that code yet, so there might be a typo or other kind of bug in there.

2 comments:

  1. The score notification extension is much needed. The ranking assignment code must be in SetGlobalsAndFillArrays in the roodylib shell.

    Making it easier to put stuff in the status line is always a welcome improvement. I've not yet looked into doing so at all, but I've been contemplating writing an IF-rougelike hybrid, where the status line would contain a semi-randomized ASCII rougelike layout.

    But you should work on your own game! You've done more than enough for The Cause. ;)

    ReplyDelete
    Replies
    1. (about the ranking assignment being in SetGlobalsAndFillArrays)

      Yup, and MAX_SCORE would have to be set there, too.

      Yeah, sounds like you have some ideas similar to things I've been working on. The nice thing about putting it all in the status window is that the resulting game is then compatible with both regular and glk interpreters. Even when you do a nice-looking multiple window game, you could have a simple ASCII graphics status window version for glk.

      Ah, ok, I'll work on my game, heh.

      Delete