Banner - Inform Glux/Glk for Dunces. Banner includes a dunce cap and the three, say no evil, hear no evil, see no evil monkeys.


    Monkey leaving trail of banana skins.
  1. But with all these new capabilities, how do I keep track of what is going on?

    With regular Inform error checking, glk gestalt testing, glk class iteration, and Glulx's Inform's new built-in glk entry points.

    The most important thing to remember about glk objects is that they are dynamic -- created "on-the-fly" in memory and stored in memory.

    1. Regular Inform Error Checking - Since glk objects are dynamic, a lot of error checking is required.

      A regular Inform object, such as a room, is all-text and platform-independent, so it can be saved to a disk file. But a glk object -- let's say a graphic window with an image in it -- is opened (window) and drawn (image) dynamically by the glulxe interpreter as the player's platform allows. So it may be affected by:  the player's operating system which may use an interpreter that doesn't support graphic windows, or the size of the player's monitor, etc. So, since glk objects are platform-dependent and managed in memory, they cannot be saved to disk.

      What is saved? The global variables that "point" to each glk object. In Zarfian these are called references. For instance, to create a graphic window, you would need to include a global variable and a constant rock number for it.

      Note that none of the following code in this section has been tested (although it has been updated to correct an error someone spotted and another I spotted).

      Constant GG_MYGRAPHWIN_ROCK 210;
      Global gg_mygraphwin = 0;
      
      Then, somewhere in your code, usually in Initialise, you would actually open the window.

      gg_mygraphwin = glk_window_open(gg_mainwin,
                      winmethod_Above+winmethod_Proportional,
                      30, wintype_Graphics, GG_MYGRAPHWIN_ROCK);
      
      The variable, gg_mygraphwin, is what will be saved to disk. It will be either:
      0 = non-existent/closed or # (object reference number) = created/open.

      Because Initialise is called before the first turn, to the player it looks like the game started up with your window there. But actually it was created in memory by the Glulxe interpreter and its glk library right after the game was loaded.

      A glk object's global variable needs to be checked to see that it is not zero before you attempt to use the object itself, as it may not have been successfully opened (due to memory constraints, screen size, etc.). It even needs to be checked before subsequent use, because something may have happened in the interim.
      if (gg_mygraphwin==0) {
         print "Ooops! We have a problem, graphic window
                for image not available. Picture not shown.^";
                return;
      }
      glk_image_draw(gg_mygraphwin, my_greatpic, 0, 0);
      

    2. Glk Gestalt Testing - The glk gestalt is really glk's potential configuration, as all interpreters may not implement all glk's capabilities. So to keep your game from crashing, you need to check glk's gestalt to test if the player's interpreter supports:  graphic images, mouse input, hyperlink selection, and sound playing. Also, some platforms may only be able to play certain type of sounds.

      The same function, glk_gestalt, is used to test for all these capabilities. The first argument determines which glk capability is being tested.

      result = glk_gestalt(gestalt_Sound, 0);
      
      If the player's interpreter doesn't support a capability, you can include code to:  exit the game (graphic games, if graphic images aren't supported); use if-elses to include/exclude that capability; or have a text response instead.

      if (glk_gestalt(gestalt_Sound, 0) && gg_myschannel)
         glk_schannel_play(gg_myschannel, babble);
      else
         print "You hear a babble of voices.^";
      
      Regular Inform error checking can be combined with the gestalt function to make sure everything is "on-line". It is not a bad idea to always call the gestalt function prior to accessing a glk object. For instance, the player may restore his/her game on a different computer, another program may allocate the sound card during game play, etc.

      Every way glk_gestalt can be called is shown in the attached wrapper reference.

    3. Glk Class Iteration - Each glk object is also a member of a class. A glk window is a member of the glk window class, a glk sound channel is a member of the glk sound channel class, etc. A glk iteration function has been provided so you can loop through all the members of a glk class and affect each object. This not only saves on coding, it becomes necessary when dealing with sound channels in IdentifyGlkObject.

      obj = glk_schannel_iterate(GLK_NULL, GLK_NULL);
      while (obj)
      { ! do something with sound channel obj
         obj = glk_schannel_iterate(obj, GLK_NULL);
      }
      
      The second word in the function name (as defined in infglk) varies depending on which class it is, such as glk_ window _iterate. If the first argument is GLK_NULL, glk_ class _iterate returns the first object of that class.

      Note:   Inform's NULL equates to -1, while the NULL used by glk is a c NULL, which equates to 0. So John Catre has provided the constant GLK_NULL in infglk.h.

    4. Glulx Inform's Glk Entry Points

      1. InitGlkWindow - {Window Objects}   InitGlkWindow is called by the Glulx Inform library before Initialise to set up the default windows. If you include your own InitGlkWindow, it will be called instead. So, if desired, this is where you may change the main text window's default text styles and colors. (Remember, style hints only affect text buffer windows that are opened subsequently, just as setting graphic windows' background colors only affects their subsequent clears/resizes.)

        Three default windows are defined in parserm.h and created by Initialise:   gg_mainwin (main text window : text buffer), gg_statuswin (status line : text grid), and gg_quotewin (quote box : text buffer). If you include your own routine, you must provide a value for gg_mainwin, or the game will exit. But a status line isn't required, and quote boxes are created/destroyed before/after each use. For details see Zarf's "The Game Authors Guide to Glulx Inform".

      2. IdentifyGlkObject - If you create any of your own glk objects you *must* include IdentifyGlkObject . After a restart/restore/undo, the Glulx Inform library restores the state of the default windows (main text window, status line, quote box) and the default streams & file references (save, script, command record, script file name), but calls IdentifyGlkObject for any glk objects it doesn't recognize. Unrecognized glk objects are the ones created by you, the game author. Since glk objects are created and stored dynamically in memory, it is understandable that the Glulx library can't restore them. It can only recognize the ones it defined and created.

        To simplify this explanation, let's resort to Zarfian "speak". ;-) A reference is the global variable that points to a glk object. When a glk object is opened/created its reference gets filled in with a number that points to that object's address in memory. (Again, that's not quite it, but close. For those who are interested, it actually points to an entry in a hashing table.) After restart/restore/undo, references can be reloaded with previous values that may not match their objects' actual states as they currently are in memory. For instance, gg_mygraphwin could be restored with a number, indicating the window it points to is open, but since game start that window has been closed. Since it was created and stored in memory, the restore will not recreate the window, it will just restore the variable that points to it.

        Phases:  IdentifyGlkOject is called in three passes, called phases:  0,1, 2. Phase zero should clear references to zero. Phase one loops through all your glk objects in memory, identifying each by rock number, and resets their references to match their objects' current states in memory (# = open/created, 0 = closed/non-existent). Phase two allows you to correct those references so they match their previous states as recovered by restart / restore / undo. This can be accomplished by:  opening, closing, creating, destroying, arranging, redrawing, playing so your glk objects correlate with their references.

        Phase zero and one deal only with (0) window, (1) stream, and (2) file reference objects. Phase two is for other glk objects, such as sound channels. (Sound channels are not handled automatically by the Glulx Inform library.) So if you haven't created any sound channels, you might ignore this phase. However, if you have changed the state of any of your other glk objects since game start, phase two shouldn't be skipped.

        Example : Fifty turns into the game, you open a text grid window called gg_mygridwin and fill it with a password, *******, for the player to figure out. When he/she does, the window is removed. But when the player restores the game he/she saved while still trying to solve the puzzle, the text grid window is not on the screen. Why? When the game was restored, the global variable gg_mygridwin was reloaded with a non-zero value indicating that the window it points to is open. But that window has been closed in memory prior to the player's restore. It is now invalid, no longer pointing to a valid memory address.

        Phase zero would clear gg_mygridwin to zero. Phase one would check the glk window referred to by gg_mygridwin, discovers it doesn't exist, and reset gg_mygridwin to its current state, 0. Phase two is where you would open the window so that it matches the restored game.

        Constant GG_MYGRIDWIN_ROCK 210;
        Global gg_mygridwin = 0;
        
        DrawGridWin();
        
        [ IdentifyGlkObject phase type ref rock
          ! clear out glk object variables
          if (phase == 0)
          {  gg_mygridwin = 0;
             return;  ! return so processing can continue
          }
          ! reset variables to current values stored in memory
          if (phase == 1) ! Glulx loops through your glk objects
          {  switch(type)
             { 0 : ! it's a window
                   switch(rock)
                   {  GG_MYGRIDWIN_ROCK : gg_mygridwin = ref;
                   }
               1 : ! it's a stream
                   ! But I didn't create any.
               2 : ! it's a file reference
                   ! But I didn't create any.
             }
             return;  ! return so processing can continue
          }
          ! restore state of glk objects themselves
          if (phase == 2)
          {   DrawGridWin();
              return; ! return so processing can continue
          }
        ];
        
        [ DrawGridWin;
           ! be sure to include this line
           if (gg_mainwin == GLK_NULL) return;
        
           ! also called by other game code
           if (((turns < 50) || got_password)) &&
               (gg_mygridwin))
           {  ! close open window
              glk_window_close(gg_mygridwin, 0);
              gg_mygridwin = 0;
           }
           else if (((turns >= 50) && (~~(got_password))) &&
                    (gg_mygridwin == 0))
             ! open window
             gg_mygridwin = glk_window_open(gg_mainwin,
                            winmethod_Right+winmethod_Fixed,
                            15, wintype_TextGrid,
                            GG_MYGRIDWIN_ROCK);
        ];
        
        Example: In Initialise, you create a graphic window, gg_mygraphwin, and draw a graphic image in it showing the first room. As the player moves around, the image changes, displaying each room. But when the player restores the game, the graphic image is not of the room he/she was in when the game was saved, because he/she has moved on. Phase zero and one would operate the same as the above. Phase two is where you would change the graphic image to match the restored room.

        Constant GG_MYGRAPHWIN_ROCK 210;
        Global gg_mygraphwin = 0;
        Global curroom_pic = 0;
        
        DrawRooms();
        
        [ IdentifyGlkObject phase type ref rock
          ! clear out glk object variables
          if (phase == 0)
          {  gg_mygraphwin = 0;
             return;  ! return so processing can continue
          }
          ! reset variables to current values stored in memory
          if (phase == 1) ! Glulx loops through your glk objects
          {  switch(type)
             { 0 : ! it's a window
                   switch(rock)
                   {  GG_MYGRAPHWIN_ROCK : gg_mygraphwin = ref;
        
                   }
               1 : ! it's a stream
                   ! But I didn't create any.
               2 : ! it's a file reference
                   ! But I didn't create any.
             }
             return;  ! return so processing can continue
          }
          ! restore state of glk objects themselves
          if (phase == 2)
          {  DrawRooms();
             return;  ! return so processing can continue
          }
        ];
        
        [ DrawRooms;
           ! be sure to include this line
           if (gg_mainwin == GLK_NULL) return;
        
           ! also called by other game code
           ! do graphics work?
           if ((glk_gestalt(gestalt_Graphic, 0) && gg_mygraphwin)
           {  ! is image information available?
              if (~~(glk_get_image_info(curroom_pic, 0, 0))
              {  print "^Picture of current room not shown, because
                         image could not be found.^";
                 return;
              }
              glk_image_draw(gg_mygraphwin, curroom_pic, 0, 0);
           }
           else
              ! graphic window not open or interpreter has changed
              print "^Picture of current room not shown, although
                    your interpreter supposedly supports
                    graphics.^";
        ];
        
        Example:   You create a sound channel in Initialise. When the player discovers an old radio in the game, he/she can turn it on and listen to music. But while it is still playing, he/she undoes to before turning it on and can still hear the music. Phase is where you would turn the music off to match the game's undone state.

        Constant GG_MYSCHANNEL_ROCK 510;
        Global gg_myschannel = 0;
        
        [ IdentifyGlkObject phase type ref rock id
          ! id is a local variable
          ! clear out glk object variables
          if (phase == 0)
          {  gg_myschannel = 0;
             return;  ! return so processing can continue
          }
          ! reset variables to current values stored in memory
          if (phase == 1) ! Glulx loops through your glk objects
          {  switch(type)
             { 0 : ! it's a window
                   ! But I didn't create any.
               1 : ! it's a stream
                   ! But I didn't create any.
               2 : ! it's a file reference
                   ! But I didn't create any.
             }
             return;  ! return so processing can continue
          }
          ! restore state of glk objects themselves
          ! since sound channels are not handled by library,
          ! have to do our own looping and value resetting here
          if (phase == 2)
          {  if (glk_gestalt(gestalt_Music, 0)
             {  ! get the first sound channel
                id = glk_schannel_iterate(GLK_NULL, gg_arguments);
                while (id)
                {  ! gg_arguments returns the rock number
                   ! array is discussed in wrapper reference
                   switch(gg_arguments-->0)
                   { GG_MYSCHANNEL_ROCK : gg_myschannel = id;
                     ! resetting to current value stored in memory
                   }
                   id = glk_schannel_iterate(id, gg_arguments);
                }
                if (gg_myschannel)
                {  if (radio_off)
                   ! turn music off
                      glk_schannel_stop(gg_myschannel);
                   else
                      glk_schannel_play_ext(gg_myschannel,
                                       loudmusic_snd, -1, 1);
                   ! turn music on
                   ! -1 = will play endlessly
                   !  1 = sound notification true, until the
                   !      player turns it off -- notification
                   !      is trapped by HandleGlkEvent
                }
             }
             return;  ! return so processing can continue
          }
        ];
        
      3. HandleGlkEvent - {Events}  Glulx Inform, like regular Inform, handles player line/character input automatically. But it does this with two new routines:   KeyboardPrimitive, and KeyCharPrimitive. Basically KeyboardPrimitive (or KeyCharPrimitive for Yes/No or menus) is operated in a continuous loop, so that a line input request is always pending (the blinking prompt). So when the player enters a line, a character, clicks the mouse (if requested), or selects a hyperlink (if requested); whichever of the two key routines is active calls HandleGlkEvent to respond. In other words:   they take priority, call HandleGlkEvent, and process any other input events that may have superceded line or character input. Thus, essentially, HandleGlkEvent is called after every event. It also handles any non-input events that may have occurred while line/character input was pending.

        If there are any player-entered commands, and player input is not aborted, they are sent onto the Inform parser. If there are any author-generated commands, and player input is aborted, they are sent onto the parser. If there are no commands (usually because the events are not input events), nothing is sent onto the parser. In this fashion, the loop continues.

        If you use any of glk's extended input you *must* include your own HandleGlkEvent to deal with the consequences. When included, like InitGlkWindow, your routine is called instead of the library one.

        The extended events you may need to provide code for are:  mouse input, hyperlink selection, sound notification (sound finishes playing), glk timer, text buffer/ text grid / graphic and window resizes, and graphic window redraws.

        1. Arguments: (Passed to HandleGlkEvent)

          Three arguments are passed to HandleGlkEvent by the Glulx Inform library: ev, context, abortres. You may see other parameters listed, but, although they follow certain naming conventions, they are actually variables local to the routine and not passed arguments.

          [ HandleGlkEvent  ev context abortres newcmd cmdlen; 
          newcmd and cmdlen are local variables
          
          1. Event Array - The first argument passed is ev, the event array. It is defined in parserm.h as gg_events and used by that name elsewhere, but when passed through HandleGlkEvent it is called ev. This array isconsidered to have a "structure", as certain elements of the array are used for specific things.

            gg_event-->0 event type
            gg_event-->1 window event is associated with
            gg_event-->2 value 1 = depends on usage
            gg_event-->3 value 2 = depends on usage
            
            Event Type:

            The event type is defined by constants in infglk.h. They have the suffix of evtype_.
            None        - none
            Timer       - event repeated at fixed intervals
            CharInput   - keystroke input in a window
            LineInput   - full line of input in a window
            MouseInput  - mouse input in a window
            Arrange     - some windows sizes have changed
            Redraw      - graphic windows need redrawing
            SoundNotify - sound finished playing
            Hyperlink   - selection of a hyperlink in a window
            
            1. Line/Character Input - You save yourself a lot of trouble if you just let Glulx Inform handle line input just like regular Inform. Also, KeyCharPrimitive will probably provide all the single character input functionality you will need.

            2. Arrange - Arrange signals when the player resizes the overall application window (for the interpreters that support that), which, of course will resize all the windows inside it. When text buffer windows are resized, the text just flows into a shorter lines. But when text grid and/or graphic windows -- which, except for the status line, can only be author-created -- are resized they may have their screen areas cut-off with information lost. So arrange lets you know when you might need to rearrange (& resize) them.

            3. Redraw - Redraw is only for graphic windows. In addition to being resized, a graphic window may be disrupted when the player fiddles with their monitor resolution and/or color settings (or hits their monitor), possibly removing the graphic image from the window or changing its appearance. So redraw lets you know when graphic images may need to be redrawn in graphic windows.

            4. Sound Notification - Notifies when a sound has finished playing.

              Mouse Input / Hyperlink Selection - Both are self-explanatory -- the player clicks an spot in window or on hyperlink.


            Associated Window:   All events, except for the glk timer and sound channels, are associated with a specific window.

            Values:  Value one and two are only used with some input events. For mouse input, value one and two will be the x, y coordinates in the window where the mouse was clicked (character positioning for text grid; pixel for graphic). For hyperlink selection, value one will be the number associated with that hyperlink. For sound notification, value one will be the sound channel's rock id number and value two will be a non-zero value if sound notification is desired (notify HandleGlkEvent when a sound finishes playing). And, since windows are not relevant to sound channels, the window pointer (ev-->1) will be GLK_NULL.

          2. Context - The second argument passed, context, tells whether the event took place while a line input request (0) or a character input request (1) was pending. Since HandleGlkEvent is called either during KeyboardPrimitive or KeyCharPrimitive, one or the other will apply. Usually, but not necessarily always, these requests will be in the main text window. And usually it will be a line input request since it is just about always pending (the blinking prompt). If you want to abort player input, when context = 0 you might cancel a line input request. When context = 1, you might cancel a character input request. Although you will have to determine which window to cancel the request for.

          3. Abortres - The third argument passed is the input buffer. It can return a command as if the player typed it in at the keyboard. If not used for that purpose it can be ignored.

            newcmd = "command";
            cmdlen = PrintAnyToArray(abortes+WORDSIZE,
                     INPUT_BUFFER_LEN-WORDSIZE,newcmd);
            abortres-->0 = cmdlen;
            
            The above puts a string into the input buffer so it can be sent to the Inform parser as an author-generated command. (If this is done, HandleGlkEvent should also return 2 to abort player input -- see Return Values below.) This also demonstrates a nifty little trick -- PrintAnyToArray will return the exact byte length of anything (see Zarf's Game Author's Guide to Glulx Inform).

        2. Return Value - The return value from HandleGlkEvent can be return (nothing), rtrue (1), or rfalse (0); which will not affect player input. Or 2, which will abort player input so that a command in abortres can be treated as if the player typed it in at the keyboard. Or -1, which will allow player input to continue even after pressing enter (line input) or entering a keystroke (character input). However, -1 is not usually used.

        3. Pending Input Requests - Line, character, mouse, and hyperlink input can all be pending at the same time, but usually not in the same window at the same time. A quick look at the chart used earlier shows why.

          Window type Input type supported Not supported
          text buffer line input
          character input
          hyperlink selection
          mouse input
          text grid line input
          character input
          mouse input
          hyperlink selection
          graphic mouse input line input
          character input
          hyperlink selection


          Only text grid windows support all types of input. But there are other limitations as well. It is illegal (a BIG problem) to request line and character input in the same window at the same time (text buffer; text grid). Since most interpreters can't prioritize mouse and hyperlink input requests in the same window at the same time (text grid; because they both use the mouse), that is also not recommended. (Same time means the other input request is still pending, i.e. not been completed yet. It is considered complete when responded to by HandleGlkEvent. Concurrent requests in different windows are okay.)

        4. Canceling Input Requests - Text cannot be written to a window when line or character input requests are pending in that window. So canceling whichever one is pending when HandleGlkEvent responds to mouse or hyperlink input instead, is not a bad idea. If you don't plan to print to the window in question, canceling is not necessary, but it is still might not a bad idea.

      Example:   Letting the player select a hyperlink to move in a direction.

      glk_set_hyperlink(1);
      print "north";
      glk_set_hyperlink(0);
      print "  ";
      glk_set_hyperlink(2)
      print "south";
      glk_set_hyperlink(0);
      
      
      [ HandleGlkEvent ev, context, abortres newcmd cmdlen;
         ! newcmd, cmdlen are local variables
         ! ev-->0 = event type
         ! ev-->1 = window of event
         ! ev-->2 = number of link
         switch(ev-->0)
         {  evtype_Hyperlink :
               ! cancel input in main text window to be safe
               if (context = 0)
                  glk_cancel_line_event(gg_mainwin);
               else
               ! also requesting char input in text grid window
               ! so cancel char request in that window
                  glk_cancel_char_event(ev-->1);
               ! create command of direction to go
               switch(ev-->2)
               {   1 : newcmd = "n";
                   2 : newcmd = "s";
               }
               ! put command in input buffer
               ! PrintAnyToArray returns actual number of
               ! bytes in "n" or "s"
               cmdlen = PrintAnyToArray(abortres+WORDSIZE,
                          INPUT_BUFFER_LEN-WORDSIZE, newcmd);
               ! put length of command in buffer
               abortres-->0 = cmdlen;
               ! return 2 to abort any pending player input
               ! so this command will take effect instead
               return 2;
          }
      ];
      
      Example:  - Letting the player use the mouse to pick up an object.

      ! First open a graphic window and draw a graphic in it.
      
      [ HandleGlkEvent ev, context, abortres newcmd cmdlen;
         ! newcmd, cmdlen are local variables
         ! ev-->0 = event type
         ! ev-->1 = window of event
         ! ev-->2 = number of link
         switch(ev-->0)
         {  evtype_MouseInput :
               ! for double click
               glk_request_mouse_event(ev-->1);
               ! area of object - x, y coordinates of pixel
               if ((ev-->2 > ### && ev-->2 < ###) &&
                   (ev-->3 > ### && ev-->3 < ###)) &&
               {  ! cancel input in main window to be safe
                  if (context = 0)
                     glk_cancel_line_event(gg_mainwin);
                  else
                     glk_cancel_char_event(gg_mainwin);
                  ! create take command
                  newcmd = "take lantern";
                  ! put command in input buffer
                  ! PrintAnyToArray returns actual number of
                  ! bytes in "take lantern"
                  cmdlen = PrintAnyToArray(abortres+WORDSIZE,
                             INPUT_BUFFER_LEN-WORDSIZE, newcmd);
                  ! put length of command in buffer
                  abortres-->0 = cmdlen;
                  ! return 2 to abort any pending player input
                  ! so this command will take effect instead
                  ! -- removing lantern from image would be
                  ! handled DrawCave called in the lantern's
                  ! after; by switching two images, one with
                  ! lantern, one without
                  return 2;
               }
            ! does not affect player input
            evtype_Arrange:
              DrawCave();
              return;
            evtype_Redraw:
              DrawCave();
              return;
          }
      ];
      

    Monkey about to skip rope.
  2. This is getting complicated. What can I skip? What can't I skip?

    Can't Skip

    1. Arguments - Glk functions have a set number of arguments. Unlike regular Inform routines, if a argument is zero you may not drop it off the end. If a glk function has five arguments, you must pass it five arguments (even if all but the first are zero). So this also applies to the wrappers in infglk.h. Update 8/3/00 - infglk.h, version 0.6.1, allows trailing zero arguments to be dropped.

    2. Gestalt Testing - Unless you want your game to burp embarrassingly, you need to test if the player's interpreter supports a specific glk capability (see Gestalt Testing above).

    3. Glk Entry Points - If you create any of your own glk objects you *must* include IdentifyGlkObject. If you use any of glk's extended input capabilities you *must* include your own HandleGlkEvent. If you insert any graphic images you *must* include your own graphic image redraw routine. (see Glk Entry Points above)

    Can Skip

    1. I Don't Do Windows - However, again, thankfully for us dunces, some window creation is automated by Glulx Inform (see InitGlkWindow above). So you only have to worry about glk's window functions if you want to create windows of your own.

    2. A Whole Lot of Streaming Going On - Ditto with streams. Since the bi-platform Glulx Inform library has been rewritten to use glk streams for normal file I/O (saving, restoring, scripting), you only need to use glk's stream functions when you want to affect window streams and/or write specialized disk files. Printing to a text window other than the main one can easily be accomplished by:   switching to the other window [glk_set_window(gg_mywin)], printing, then switching back [glk_set_window(gg_mainwin)] to the main text window to resume normal I/O.


     Part One      Part Two      Part Four      Top of Page
     Appendix One (infglk Wrapper Reference)
     Appendix Two (Interpreter & Color Charts)

Monkey in dunce cap. doeadeer3@aol.com