Include "infglk";
System_file;
Message "[Including <unglklib>]";
!-------------------------------------------------------------------------
! unglklib.h
!-------------------------------------------------------------------------
! copyright 2001 by Marnie Parker, all rights reserved.
!
! You have permission to download and include this file in your own
! Inform Glulx game. You may modify the source code for your personal use.
! Public modifications and updated releases may only be made by the author.
!-------------------------------------------------------------------------
! 8/01/01 version 1.51                                   doeadeer3@aol.com
!-------------------------------------------------------------------------

! This "strictly-Glulx" (not bi-platform) library is meant to be used
! "out of the box." It provides an interface to Glulx Inform's built-in
! Glk multimedia capabilities:  windows, graphics, sounds, keyboard, mouse,
! hyperlink input, the "real time" Glk timer, and other useful features.

! In many cases, I have simply "wrapped" infglk wrappers into more
! Inform-like routines ("wrapped the wrappers") -- giving friendlier
! names and formats to infglk's lower-level interface, which in turn
! wraps the even lower-level Glk interface built into Glulx.

! In others, I have combined several infglk functions with additional
! Inform coding to perform multiple tasks ("Glulx library extensions").

! However, I haven't created new constants to replace those in infglk.h,
! used Glulx op codes, or done any fancy programming. This means unglklib
! should be all the more readable by the "average" Informer.

! If you want to learn Glk, you can study the infglk functions wrapped in
! the following routines. If you don't want to delve into Glk more than you
! have to, you can squint your eyes at the wrapped infglk functions, ignore
! them, and just focus your gaze on the surrounding unglklib routines.

! The routines are in two sections:  1. Testing - the Glk gestalt
! (the player's interpreter)  2. Glky Routines - drawing/sounds/etc.

! Future updates -----------------------------------------------------------

! This library is far from complete -- it is just a START. If you find any
! programming errors in the source code and/or factual errors in the
! comments, please contact me at my email address above.

! If you have any significant improvements to suggest, and/or Inform Glulx
! routines/functions that you would be willing to add, also please email
! me. With your permission, the second may be included (with full annotated
! credit) in any future updates.

! :-) Doe

!-------------------------------------------------------------------------
! Version History
!-------------------------------------------------------------------------

! 1.51  Slight editing changes
! 1.50  First released version

!-------------------------------------------------------------------------
! Using this library
!-------------------------------------------------------------------------

! This library comes with the file, "briefglk.txt." It is not a manual for
! the following routines, but a brief overview of Glk.

! Also, almost all the routines below are illustrated (and tested) by the
! source code of the demo Glulx game, "Just a Dream." Search through that
! source for an example of the desired routine, such as "ActivateHyperlinks",
! or just the thing you want to more know about, such as "Hyperlinks."

! Combined, the two -- "Just a Dream's" source code, and the brief Glk
! overview -- should help the Glulx/Glk novice become an initiate.

! Format of this file ------------------------------------------------------

! A summary of the unglklib routines/functions is in the section below.

! Below that are the actual routines/functions themselves. Above each one
! is an example of how the routine would actually be called in Inform. The
! arguments shown in the example are, however, generic, not specific.

! --- ThisRoutine(argument1, argument2);  *

! An asterisk is next to the example if its true/false return value
! is of minimal use in Inform (see section below for an explanation).

! [ Thisroutine argument1 argument2 argument3 local;

!                 arg2 = infglk constant/with prefix this_ (this_Value1)
! argument1 = win = Glk obj (global variable)
! argument2 =   1 = Value1 - means such and such
!               2 = Value2 - means such and such
! argument3 =   1 = yes, turn on
!               0 = no, turn off
! ---------
! local     = value

! Argument List - Inside each routine is a list of what value should be passed
! to each argument. Every passed value is represented as either a Glk object
! or an integer (in this file, decimals). When local variables are used, a
! line separates them from the arguments.

! Global Variables - If an argument should be passed a global variable that
! will be noted -- argument1 = Glk obj {global variable} -- example:  win.

! infglk Constants - When an argument *may* be passed an infglk constant,
! instead of a decimal, that will be noted at the top:  arg2 = infglk constant
! /with prefix_ this_ -- with an example:  this_Value1. Note that the arguments
! that may use constants also have capitalized names:  Value1 or Value2.

! This means you may pass argument1 a decimal number, 1 or 2, OR an infglk
! constant with a name composed of the prefix this_ added to one of the
! capitalized names:  this_Value1, or this_Value2. Essentially this tells you
! the optional infglk constant name, without you having to learn it ;-).

! Glk Objects/Events - For those who want to delve more into Glk, whether the
! things the routines affect are Glk objects or events has also been noted
! above each section in its section header, [Glk objects].

! For more about infglk constants and other Glky things like
! IdentifyGlkObject() and HandleGlkEvent(), see "briefglk.txt."

! Optional true/false return values ----------------------------------------

! Glk returns - Glk is very self-testing. The infglk functions (based on the
! one Glk function built into Glulx Inform) all have return values. Often they
! just return true/false, reporting the success/failure of their execution.

! unglklib returns - All routines below followed by (*) or (**), also return
! true/false, reporting the success/failure of the infglk functions they
! "wrap." The asterisks indicate that in Inform these returns may not be useful,
! or even relevant. This is due to several factors: 1.) 99 times out of a 100,
! the routine will perform as expected, so testing it would entail redundant
! coding; a true/false return may be useful for interpreter c-programmers but
! not especially for Informers; and/or it is not reporting what one expects.

! Examples:
! 1. The interpreter has already tested to see if it supports mouse input
!    in a graphic window with SupportsMouseGraphics(). So 99 times out of 100
!    (or more) when ActivateMouse() is called a graphic window, it works.
! 2. CloseWindow() closes an existing window. Immediately after, the game
!    author should set that window's variable, gg_mywin, to zero. So, in
!    Glulx Inform, it is the variable, gg_mywin, that is tested to see if
!    the window is open/closed, not the result of CloseWindow().
! 3. SetTextStyle___() suggests a new text style. But its return value only
!    indicates whether or not a new text style was *suggested*. To find out
!    that text style suggestion *succeeded*, TestTextStyle___() must be used.

! Debatable true/false returns - The routines with (**) following them,
! have true/false returns of debatable usefulness. For instance,
! SupportsSounds() tests the interpreter to see if it can play sounds.
! Playsound() actually plays a sound in a sound channel (if the sound file
! exists). If SupportsSounds() was already called, then testing PlaySound()'s
! result may be superfluous. If the interpreter cannot find the sound file,
! PlaySound() does return false, but that just means silence -- the game will
! play anyway. (Note:  See comments on the possibility of missing
! graphics/sounds under the "External resources" section in "unglklib.")

! It is *sound notification*, not PlaySound(), that can trigger some action
! in response to a sound. (HandleGlkEvent() is notified when a sound
! *finishes* playing.)

! Routines are also marked with * or ** by their examples. (Yes, this is a
! judgement call on my part, but I base it on actually using Inform Glulx.)
! When to test a true/false return value is, of course, up to the individual,
! the asterisks are just to save the game author wasted time.

! Required return values - All other routines return either a Glk object or
! the true/false results of a test. These return values are necessary.

! Programming DrawableObjs -------------------------------------------------

! DrawableObj Class - DrawableObjs are takeable Inform objects with
! accompanying pictures. This class is used by three routines:
! DrawLocaleObjs(), DrawLocaleObjsWin(), and DrawInventoryObjsWin(). All behave
! slightly differently. (Note:  See DrawableObj class under Object Graphics.)

! Locale DrawableObjs - Locale refers to objects in the location. When Inform
! <<Look>>s, the last part of LookSub() calls the Locale() routine to loop
! through all the objects in the current location and report their presence
! if they do not have static, scenery, or concealed attributes.

! "You see a hat here."

! DrawLocaleObjs() will draw DrawableObjs in the current location in the main
! text window along with the game text, right after the above message.
! DrawLocaleObjsWin() will draw them in a window separate from the game text.

! Along with game text | or separate. ->      DrawLocaleObjsWin()
!                      V                      ----------------------
!                                            | Location:            |
! DrawLocaleObjs()                           |   picture of hat     |
!                                             ----------------------
! >look                                       >look
! "You see a hat here." picture of hat        "You see a hat here."

! Both are invoked by replacing Library Message, Look > #4, the one
! printed by Locale(). After the WriteListFrom() (make sure the message does
! not return) include whichever routine you want to use, then return.

!   WriteListFrom(child(lm_o),
!      ENGLISH_BIT + WORKFLAG_BIT + RECURSE_BIT
!      + PARTINV_BIT + TERSE_BIT + CONCEAL_BIT);
!   if (lm_o~=location) print ".";  ! print rather than
!   else print " here.";            ! print return
!   DrawLocaleObjs()                ! draws in a main text window
!   OR
!   DrawLocaleObjsWin(gg_mywin);    ! draws in separate window
!   " ";  ! now print return

! Inventory DrawableObjs - DrawInventoryObjsWin() operates similarily to
! DrawLocaleObjsWin(), except it draws DrawableObjs in the players inventory,
! and does not depend on a library message. However, it should be called every
! time a DrawableObj is taken/dropped (again, see the DrawableObj class below).

! DrawInventoryObjsWin()
!  ----------------------
! | Inventory:           |
! |   picture of hat     |
! |   picture of wallet  |
!  ----------------------

! Naturally, windows used by both windowing routines must be opened first.

! Darkness & windowing DrawableObjs routines - All routines work fine in
! lighted areas, but there is a problem when the player is in the dark. When
! location == thedark, the Locale() routine doesn't report the objects in the
! location, because it returns before it even gets that far. So, unfortunately,
! a "hack" must be used. DrawLocaleObjs will be okay, since Locale() never got
! that far, the Look message will not be printed and DrawableObj in the main
! text window will not be drawn.

! But the windowing routines, DrawLocaleObjsWin() and DrawInventoryObjsWin(),
! will still show DrawableObjs. And because thedark is not a real location,
! the locale window used by DrawLocaleObjsWin() will show DrawableObjs in the
! *last lighted room* the player was in. Not good -- in the dark the player
! isn't supposed to be able to see *any* object. (Note:  thedark is hard-coded
! into the Inform library in various strange places in various strange ways.)

! So instead of trying to alter the complicated visibility levels in the
! Locale() routine, the easiest thing to do is Replace NoteArrival() and alter
! it, since it is called by LookSub() right after Locale(). Then, because both
! windowing routines have a flag for thedark, they will say, "Darkness," and
! not draw any DrawableObjs.

! DrawInventoryObjsWin()
! DrawLocaleObjsWin()
!  ----------------------
! | Darkness             |
! |                      |
! |                      |
!  ----------------------

! Replace ScoreArrival;

![ ScoreArrival;
!  if (location hasnt visited)
!  {   give location visited;
!      if (location has scored)
!      {   score = score + ROOM_SCORE;
!          places_score = places_score + ROOM_SCORE;
!      }
!  }
!  if (location == thedark)
!  {  DrawInventoryObjsWin(gg_myinvwin);
!     DrawLocaleObjsWin(gg_mylocalewin);
!  }
!];

! Or if the game is permanent verbose mode, just replace Library Message,
! miscellaneous #17. This is much easier, too.

! LibraryMessages
! ...
! print "It was pitch dark, and you couldn't see a thing.";
!        DrawInventoryObjsWin(gg_myinvwin);
!        DrawLocaleObjsWin(gg_mylocalewin);
!        " ";

! I apologize for the Replace NoteArrival() hack, but I make no apologies
! for these routines having problems with thedark.

! Programming Timed Events -------------------------------------------------

! All the timed special effects in this library (AnimateGraphics(),
! FlashWindowColors(), TickerTape()), use basically the same programming
! technique. Each has an accompanying global counter, such as flash_count,
! initialized to NULL. (For various reasons -1 is better than zero.)

! To start a special effect, first start the timer at a certain speed. Then
! set the appropriate counter to 1. Next call the special effect routine.

! if (SupportsGlkTimer())              (Animate_count is the exception.
! { StartGlkTimer(desired_speed);      It should be initialized to the first
!   special_effect_count = 1;          picture in the animation series, such
!   DoSpecialEffect();                 as animate_count = first_pic.)
! }

! What happens is this executes the special effect once, then every ###
! milliseconds the Glk timer will execute it again (and again) until the
! counter is reset back to NULL. The counter may be reset wherever/whenever
! in the game it is appropriate to turn it off.

! However, this does not happen automatically, code must also be put in
! HandleGlkEvent(). The timer "tick" passes to it to see what the timer is
! supposed to do. So the special effect routine also goes there. NULL becomes
! the test condition. After resetting the counter to it -- no matter where
! in the game code -- the next timer "tick" passed onto HandleGlkEvent()
! uses it as the condition to turn the timer off. The special effect stops.

! HandleGlkEvent()
! ...
! evtype_Timer:
!   if (special_effect_count == NULL) StopGlkTimer();
!   else DoSpecialEffect();

!---------------------------------------------------------------------------
! S u m m a r y  of unglklib functions/routines
!---------------------------------------------------------------------------

! Testing the Glk gestalt (the player's interpreter) -----------------------

! SupportsGraphics();                                   (returns true/false)
! SupportsWinGraphics();                                (returns true/false)
! SupportsTextGraphics();                               (returns true/false)
! SupportsGlkTimer();                                   (returns true/false)
! SupportsGridMouse();                                  (returns true/false)
! SupportsGraphicMouse();                               (returns true/false)
! SupportsHyperlinks();                                 (returns true/false)
! SupportsTextHyperlinks();                             (returns true/false)
! SupportsGridHyperlinks();                             (returns true/false)
! SupportsSounds();                                     (returns true/false)
! SupportsSoundNofity();                                (returns true/false)
! SupportsMODs();                                       (returns true/false)

! GlkTimer/Mouse/Hyperlinks/Line & Char Input ------------------------------

! StartGlkTimer(speed);                                                  (*)
! StopGlkTimer();                                                        (*)

! ActivateMouse(win);                                                    (*)
! CancelMouse(win);                                                      (*)

! ActivateHyperlinks(win);                                               (*)
! Hyperlink(onoff);                                                      (*)
! CancelHyperlinks(win);                                                 (*)

! CancelLineInput(win);                                                  (*)
! CancelCharInput(win);                                                  (*)

! Windows ------------------------------------------------------------------

! OpenTextWindow(parent_win, dir, fixed, size, rockid);     (returns window)
! OpenGridWindow(parent_win, dir, fixed, size, rockid);     (returns window)
! OpenGraphicWindow(parent_win, dir, fixed, size, rockid);  (returns window)
! GetWindowSize(win, widthptr, heightptr);                               (*)
! SwitchWindow(win);                                                     (*)
! ClearWindow(win);                                                      (*)
! CloseWindow(win);                                                      (*)
! IsWinText(win);                                       (returns true/false)
! IsWinGrid(win);                                       (returns true/false)
! IsWinGraphic(win);                                    (returns true/false)

! Graphic Windows ----------------------------------------------------------

! SetWindowBackColor(win, color);                                       (**)
! ChangeWindowBackColor(win, color);                                    (**)
! EraseWindowRect(win, left, top, width, height);                        (*)
! FillWindowRect(win, color, left, top, width, height);                  (*)
! EraseTotalWindow(win);                                                 (*)
! FloodFillWindow(win, color);                                           (*)

! Graphics -----------------------------------------------------------------

! DoesGraphicExist(pic);                                (returns true/false)
! DrawWinGraphic(win, pic, xpos, ypos);                                 (**)
! DrawTextGraphic(win, pic, inlinepos);                                 (**)

! Object Graphics (DrawableObj Class) ---------------------------------------

! DrawLocaleObjs();
! DrawLocaleObjsWin(win);
! DrawInventoryObjsWin(win);

! Timed Graphics ------------------------------------------------------------

! AnimateGraphics(win, curr_pic, forward,             (returns # of next pic)
!                 first_pic, last_pic, repeat);
! FlashWindowColors(win, color1, color2);

! Cursor --------------------------------------------------------------------

! MoveCursor(win, xpos, ypos);                                           (**)

! Player's Input ------------------------------------------------------------

! GetPlayersInput();
! ValidatePlayersInput();

! Text ----------------------------------------------------------------------

! SetTextStyleColor(style_cat, color);                                    (*)
! SetTextStyleBackColor(style_cat, color);                                (*)
! SetTextStyleReverseColor(style_cat, onoff);                             (*)
! SetTextStyleSize(style_cat, size);                                      (*)
! SetTextStyleWeight(style_cat, weight);                                  (*)
! SetTextStyleLight(style_cat);                                           (*)
! SetTextStyleNormal(style_cat);                                          (*)
! SetTextStyleBold(style_cat);                                            (*)
! SetTextStyleItalic(style_cat, onoff);                                   (*)
! SetTextStyleProporFixed(style_cat, which);                              (*)
! SetTextStyleProportional(style_cat);                                    (*)
! SetTextStyleFixed(style_cat);                                           (*)
! SetTextStyleJustified(style_cat, just_side);                            (*)

! TestTextStyleColor(win, win, style_cat);                    (returns color)
! TestTextStyleBackColor(win, style_cat);                     (returns color)
! TestTextStyleReverseColor(win, style_cat);             (returns true/false)
! TestTextStyleSize(win, style_cat);                           (returns size)
! TestTextStyleWeight(win, style_cat);                       (returns weight)
! TestTextStyleLight(win, style_cat);                    (returns true/false)
! TestTextStyleNormal(win, style_cat);                   (returns true/false)
! TestTextStyleBold(win, style_cat);                     (returns true/false)
! TestTextStyleItalic(win, style_cat);                   (returns true/false)
! TestTextStyleProporFixed(win, style_cat);                    (returns 1, 0)
! TestTextStyleFixed(win, style_cat);                    (returns true/false)
! TestTextStyleProportional(win, style_cat);             (returns true/false)
! TestTextStyleJustified(win, style_cat);             (returns justification)

! ClearTextStyleColor(style_cat);                                         (*)
! ClearTextStyleBackColor(style_cat);                                     (*)
! ClearTextStyleReverseColor(style_cat);                                  (*)
! ClearTextStyleSize(style_cat);                                          (*)
! ClearTextStyleWeight(style_cat);                                        (*)
! ClearTextStyleItalic(style_cat);                                        (*)
! ClearTextStyleProporFixed(style_cat);                                   (*)
! ClearTextStyleJustified(style_cat);                                     (*)

! SelectTextStyle(style_cat);                                             (*)
! PrintTextStyle(style_cat);
! PrintTextStyleWin(win, style_cat, str, line_space);
! boldface(str);

! Timed Text ----------------------------------------------------------------

! SetTickerTapeStr(str);
! TickerTape();

! Sounds --------------------------------------------------------------------

! CreateChannel(rockid);                                    (returns channel)
! PlaySound(channel, snd, repeat, times, notify);                        (**)
! GetNextChannel(rockid, arg_ptr);                    (returns rock constant)
! DestroyChannel(channel);                                                (*)

! Files --------------------------------------------------------------------

! DoesFileExist(file_ref);                              (returns true/false)
! DeleteFile(file_ref);                                                 (**)

! CreateFileRef(file_name, usage, text_file, rockid);     (returns file ref)
! CreateFileRefByPrompt(usage, text_file, mode, rockid);  (returns file ref)
! DestroyFileRef(file_ref);                                              (*)

! OpenFileStream(file_ref, mode, rockid);              (returns file stream)
! EchoWinToFile(win, file_strm);                                        (**)
! CloseFileStream(file_strm);                                           (**)

!---------------------------------------------------------------------------
! 1. T e s t i n g - the Glk gestalt      (testing the player's interpreter)
!---------------------------------------------------------------------------

! Not all Glulxe interpreters support all of Glk's capabilities, so testing
! the gestalt (Glk configuration) tests the interpreter. (Or to be more
! accurate, testing the gestalt means testing the Glk library specific to the
! player's interpreter, but testing the interpreter is easier to understand.)

! --- SupportsGraphics();

! Tests graphic capability. Does the interpreter support Glk's "suite" of
! graphic functions?

[ SupportsGraphics;
  return glk_gestalt(gestalt_Graphics, 0);
];

! --- SupportsWinGraphics();

! Does the interpreter support drawing images in graphic windows?

[ SupportsWinGraphics;
  return glk_gestalt(gestalt_DrawImage, wintype_Graphics);
];

! --- SupportsTextGraphics();

! Does the interpreter support drawing images in text buffer windows?

[ SupportsTextGraphics;
  return glk_gestalt(gestalt_DrawImage, wintype_TextBuffer);
];

! --- SupportsGlkTimer();

! Does the interpreter support the Glk timer? The Glk timer, unlike the
! Inform timer, it doesn't depend on number of turns. It is uses real time,
! not game time.

[ SupportsGlkTimer;
  return glk_gestalt(gestalt_Timer, 0);
];

! Currently, as far as I know, all interpreters which support graphics,
! also support all forms of mouse and hyperlink input.

! Mouse input is designed only to be used text grid and graphic windows.

! --- SupportsGridMouse();

! Does the interpreter support mouse input in text grid windows?

[ SupportsGridMouse;
  return glk_gestalt(gestalt_MouseInput, wintype_TextGrid);
];

! --- SupportsGraphicMouse();

! Does the interpreter support mouse input in graphic windows?

[ SupportsGraphicMouse;
  return glk_gestalt(gestalt_MouseInput, wintype_Graphics);
];

! --- SupportsHyperlinks();

! Does the interpreter support Glk's "suite" of hyperlink functions?

[ SupportsHyperlinks;
  return glk_gestalt(gestalt_Hyperlinks, 0);
];

! Hyperlink input is designed only to be used text buffer/grid windows.

! --- SupportsTextHyperlinks();

! Does the interpreter support hyperlink input in text buffer windows?

[ SupportsTextHyperlinks;
  return glk_gestalt(gestalt_Hyperlinks, wintype_TextBuffer);
];

! --- SupportsGridHyperlinks();

! Does the interpreter support hyperlink input in text grid windows?

[ SupportsGridHyperlinks;
  return glk_gestalt(gestalt_Hyperlinks, wintype_TextGrid);
];

! --- SupportsSounds();

! Does the interpreter support playing sounds?

[ SupportsSounds;
  return glk_gestalt(gestalt_Sound, 0);
];

! --- SupportsSoundNotify();

! Does the interpreter support sound notification? (Note:  Sound notification
! means being notified when a sound finishes playing so some action can be
! triggered when it does. This can add some nice special effects.
! Also, note that HandleGlkEvent() is what is notified.)

[ SupportsSoundNotify;
  return glk_gestalt(gestalt_SoundNotify, 0);
];

! --- SupportsMODs();

! Does the interpreter support playing MOD sound files? If false, only plays
! AIFs.

[ SupportsMODs;
  return glk_gestalt(gestalt_SoundMusic, 0);
];

!-------------------------------------------------------------------------
! 2. G l k l y  R o u t i n e s - drawing/sounds/etc.
!-------------------------------------------------------------------------

!---------------------------------------------------------------------------
! GLKTIMER/MOUSE/HYPERLINKS/LINE & CHAR INPUT                   [Glk events]
!---------------------------------------------------------------------------

!---------------------------------------------------------------------------
! Glk Timer
!---------------------------------------------------------------------------

! The Glk timer can only handle one timed event at once. There is no way
! to test if the Glk timer is currently running or not. So if a game
! has more than one timed event, they cannot run concurrently.

! --- StartGlkTimer(speed); *

! Starts the Glk timer. HandleGlkEvent() processes its subsequent "ticks."

[ StartGlkTimer speed;
! speed = milliseconds (thousandths of a second)

  return glk_request_timer_events(speed);
];

! --- StopGlkTimer(); *

! Stops the Glk timer.

[ StopGlkTimer;
  return glk_request_timer_events(0);
];

!---------------------------------------------------------------------------
! Mouse
!---------------------------------------------------------------------------

! Requesting (the word used in infglk.h) essentially means "activate."

! --- ActivateMouse(win); *

! Activates (request) mouse input in a particular window. Must be called before
! each mouse input ("click") in that window or mouse input will not work.

[ ActivateMouse win;
! win = window mouse input is to be in {global variable}

  return glk_request_mouse_event(win);
];

! --- CancelMouse(win); *

! Cancels mouse input in a particular window.

[ CancelMouse win;
! win = window mouse input is in {global variable}

  return glk_cancel_mouse_event(win);
];

!---------------------------------------------------------------------------
! Hyperlinks
!---------------------------------------------------------------------------

! --- ActivateHyperlinks(win); *

! Activates (request) hyperlinks in a particular window. Must be called before
! each pair of hyperlinks, or group of hyperlink pairs, in that window, or
! hyperlinks won't work.

[ ActivateHyperlinks win;
! win = window hyperlinks are to be in {global variable}

  return glk_request_hyperlink_event(win);
];

! --- Hyperlink(onoff); *

! Turns hyperlinked text on/off. Hyperlinks come in pairs. An unique number
! in the first hyperlink turns on linked text -- a zero in the second hyperlink
! turns it off. Linked text will usually be displayed as blue, underlined text.

! Example:  Hyperlink(1); print "north"; Hyperlink(0);

[ Hyperlink onoff;
! onoff - unique number for hyperlink or 0

  return glk_set_hyperlink(onoff);
];

! --- CancelHyperlinks(win); *

! Cancel hyperlinks in a particular window.

[ CancelHyperlinks win;
! win = window hyperlinks are in {global variable}

  return glk_cancel_hyperlink_event(win);
];

!---------------------------------------------------------------------------
! Line/Char Input
!---------------------------------------------------------------------------

! --- CancelLineInput(win); *

! Cancels line input -- usually only called in HandleGlkEvent().

[ CancelLineInput win;
! win = window line input is in {global variable}

  return glk_cancel_line_event(win);
];

! --- CancelCharInput(win); *

! Cancels character input -- usually only called in HandleGlkEvent().

[ CancelCharInput win;
! win = window char input is in {global variable}

  return glk_cancel_char_event(win);
];

!-------------------------------------------------------------------------
! WINDOWS                                                    [Glk objects]
!-------------------------------------------------------------------------

! Opens a new window:  text buffer, text grid, or graphic. When a new window
! is created, it is considered to "split" the parent window.

! --- OpenTextWindow(parent_win, dir, fixed, size, rockid);

! Opens a text buffer window above, below, etc. the parent window, with a fixed
! or proportional width, and a specific size.

[ OpenTextWindow parent_win dir fixed size rockid method;
!                dir = infglk constant/prefix winmethod_ (winmethod_Left)
! parent_win  = "parent" window, window to split into another
! dir         = 0 = Left - direction put new window in relation to parent win
!               1 = Right
!               2 = Above
!               3 = Below
! fixed       = 1 = fixed width              (the size passed)
!               0 = no, proportion of parent
! size        = size of new window
!               text buffer is measured in characters if split left/right
!                                       in lines if split above/below
! rockid      = rock number (global constant)
! ------
! method      = combined dir + fixed
! returns       window

  if (parent_win == 0) rfalse;
  if (fixed) method = (dir + winmethod_Fixed);
        else method = (dir + winmethod_Proportional);
  return glk_window_open(parent_win, method, size, wintype_TextBuffer,
                            rockid);
];

! --- OpenGridWindow(parent_win, dir, fixed, size, rockid);

! Opens a text grid window above, below, etc. the parent window, with a fixed
! or proportional width, and a specific size.

[ OpenGridWindow parent_win dir fixed size rockid method;
!             dir = infglk constant/prefix winmethod_ (winmethod_Left)
! parent_win  = "parent" window, window to split into another
! dir         = 0 = Left - direction put new window in relation to parent win
!               1 = Right
!               2 = Above
!               3 = Below
! fixed       = 1 = fixed width              (the size passed)
!               0 = no, proportion of parent
! size        = size of new window
!               text grid is measured in characters if split left/right
!                                     in lines if split above/below
! rockid      = rock number (global constant)
! -------
! method      = combined dir + fixed
! returns       window

  if (parent_win == 0) rfalse;
  if (fixed) method = (dir + winmethod_Fixed);
        else method = (dir + winmethod_Proportional);
  return glk_window_open(parent_win, method, size, wintype_TextGrid,
                           rockid);
];

! --- OpenGraphicWindow(parent_win, dir, fixed, size, rockid);

! Opens a graphic window above, below, etc. the parent window, with a fixed
! or proportional width, and a specific size.

[ OpenGraphicWindow parent_win dir fixed size rockid method;
!             dir = infglk constant/prefix winmethod_ (winmethod_Left)
! parent_win  = "parent" window, window to split into another
! dir         = 0 = Left - direction put new window in relation to parent win
!               1 = Right
!               2 = Above
!               3 = Below
! fixed       = 1 = fixed width              (the size passed)
!               0 = no, proportion of parent
! size        = size of new window
!               graphic is measured in pixels in either direction
! rockid      = rock number (global constant)
! ------
! method      = combined dir + fixed
! returns       window

  if (parent_win == 0) rfalse;
  if (fixed) method = (dir + winmethod_Fixed);
        else method = (dir + winmethod_Proportional);
  return glk_window_open(parent_win, method, size, wintype_Graphics,
                            rockid);
];

! --- GetWindowSize(win, widthptr, heighptr); *

! Gets the size of an open window. Note the ptr suffix on arguments -- this
! means the size, width & height, are actually passed to the global array,
! gg_arguments. For an example, see DrawWinGraphics().

[ GetWindowSize win widthptr heightptr;
! win        = window to get size of
! widthptr   = width of window
! heightptr  = height of window

  if (win == 0) rfalse;
  return glk_window_get_size(win, widthptr, heightptr);
];

! --- SwitchWindow(win); *

! Makes the passed window the current window. All subsequent printing
! (if it is a text buffer/grid window) will go to the new current window.
! It is also perfectly "legal" to make the current window, the current window.

[ SwitchWindow win;
  if (win == 0) rfalse;
  return glk_set_window(win);
];

! --- ClearWindow(win); *

! Clears a window (doesn't matter what type). Clearing a text buffer window
! will clear it of text. Clearing a grid window will clear its character array
! and move the cursor to position 0, 0 - x (vertical), y (horizontal).
! A graphic window will be cleared to its current background color.

[ ClearWindow win;
  if (win == 0) rfalse;
  return glk_window_clear(win);
];

! --- CloseWindow(win); *

! Closes a window (doesn't matter what type).

[ CloseWindow win;
  if (win == 0) rfalse;
  return glk_window_close(win);
];

! --- IsWinText(win);

! Tests the window's type. Is it a:  text buffer, text grid, or graphic window?

[ IsWinText win type;
! win   = window to test
! ----
! type  = type of window

  if (win == 0) rfalse;
  type = glk_window_get_type(win);
  if (type == wintype_TextBuffer) rtrue;
  rfalse;
];

! --- IsWinGrid(win);

[ IsWinGrid win type;
  if (win == 0) rfalse;
  type = glk_window_get_type(win);
  if (type == wintype_TextGrid) rtrue;
  rfalse;
];

! --- IsWinGraphic(win);

[ IsWinGraphic win type;
  if (win == 0) rfalse;
  type = glk_window_get_type(win);
  if (type == wintype_Graphics) rtrue;
  rfalse;
];

!---------------------------------------------------------------------
! Graphic Windows
!---------------------------------------------------------------------

! These coloring routines only affect graphic windows.

! The difference between filling a graphic window with a color (FloodFill())
! and changing its background color appears to be negligible -- two steps
! instead of one. But it also depends on whether the game author wants to
! permanently change a window's default background color (until it is changed
! again), or not.

! --- SetWindowBackColor(color); **

! Sets the background color of a graphic window. The new background
! color will only be displayed upon subsequent window clears (ClearWindow()).

[ SetWindowBackColor win color;
! win      = window to use
! color    = color (number)

  if (win == 0) rfalse;
  if (~~(IsWinGraphic(win))) rfalse;
  return glk_window_set_background_color(win, color);
];

! --- ChangeWindowBackColor(win, color); **

! Changes the background color of a graphic window. First sets the new
! background color, then clears the window so it will be immediately displayed.

[ ChangeWindowBackColor win color result;
! win      = window to use
! color    = color (number)
! -----
! result   = true = success, false = failure

  if (win == 0) rfalse;
  if (~~(IsWinGraphic(win))) rfalse;
  result = SetWindowBackColor(win, color);
  ClearWindow(win);
  return result;
];

! --- EraseWindowRect(win, left, top, width, height); *

! Erases a rectangular area in a graphic window. The erased area will show
! the default background color of the window.

[ EraseWindowRect win left top width height;
! win      = window to use
! left     = left side of rectangle to erase (pixel position)
! top      = top of rectangle
! width    = width of rectangle
! height   = height of rectangle

  if (win == 0) rfalse;
  if (~~(IsWinGraphic(win))) rfalse;
  return glk_window_erase_rect(win, left, top, width, height);
];

! --- FillWindowRect(win, color, left, top, width, height); *

! Fills a rectangular area in a graphic window with a particular color.

[ FillWindowRect win color left top width height;
! win      = window to use
! color    = color (number)
! left     = left side of rectangle to fill
! top      = top of rectangle
! width    = width of rectangle
! height   = height of rectangle

  if (win == 0) rfalse;
  if (~~(IsWinGraphic(win))) rfalse;
  return glk_window_fill_rect(win, color, left, top, width, height);
];

! --- EraseTotalWindow(win); *

! Erases a whole graphic window. Will reveal the default background color.

[ EraseTotalWindow win width height;
! win    = window to use
! -----
! width  = width of window
! height = height of window

  if (win == 0) rfalse;
  if (~~(IsWinGraphic(win))) rfalse;
  GetWindowSize(win, gg_arguments, gg_arguments+WORDSIZE);
  width  = gg_arguments-->0;
  height = gg_arguments-->1;
  return EraseWindowRect(win, 0, 0, width, height);
];

! --- FloodFillWindow(win, color); *

! Fills a whole graphic window with a particular color.

[ FloodFillWindow win color width height;
! win    = window to use
! color  = color (number)
! ------
! width  = width of window
! height = height of window

  if (win == 0) rfalse;
  if (~~(IsWinGraphic(win))) rfalse;
  GetWindowSize(win, gg_arguments, gg_arguments+WORDSIZE);
  width  = gg_arguments-->0;
  height = gg_arguments-->1;
  return FillWindowRect(win, color, 0, 0, width, height);
];

!-------------------------------------------------------------------------
! GRAPHICS                                               [blorb resources]
!-------------------------------------------------------------------------

! --- DoesGraphicExist(pic);

! Tests whether a particular graphic file can be found, by trying to retrieve
! information about that graphic. However, whether the interpreter can find a
! particular picture (or not), is not always important.

[ DoesGraphicExist pic;
! pic     = graphic file to find
! -------
! returns  true/false could/couldn't get information about that graphic file

 return glk_image_get_info(pic, 0, 0);
];

! --- DrawWinGraphic(win, pic, xpos, ypos); **

! Draws a graphic picture in a graphic window, scaled to the size of the
! window ("stretch to fit"). Starts drawing at cursor location 0,0. (Note: It's
! prettier to scale pictures to the size of the window, instead of the reverse.)

[ DrawWinGraphic win pic xpos ypos width height;
! win    = window to use   {global variable}
! pic    = current picture {global variable}
! xpos   = x pos at which to draw pic (both zero here)
! ypos   = y pos at which to draw pic
! ------
! width  = width of window
! height = height of window

   if (win == 0) rfalse;
   if (~~(IsWinGraphic(win))) rfalse;
   if ((pic) && (~~(DoesGraphicExist(pic)))) rfalse;
   GetWindowSize(win, gg_arguments, gg_arguments+WORDSIZE);
   width  = gg_arguments-->0;
   height = gg_arguments-->1;
   ClearWindow(win);
   return glk_image_draw_scaled(win, pic, xpos, ypos, width, height);
];

! --- DrawTextGraphic(win, pic, inlinepos); **

! Draws a graphic image in a text buffer window. When graphics are drawn in a
! text buffer windows, image positioning it is similar to HTML's -- images are
! aligned with the text in the window.

[ DrawTextGraphic win pic inlinepos;
!             inlinepos = infglk constant/prefix imagealign_ (imagealign_Up)
! win       = window to use {global variable}
! pic       = current pic   {global variable}
! inlinepos = 0 = Up - align bottom of image with text baseline, rest sticks up
!           = 1 = is unused
!           = 2 = Down - align top of image with top of text, rest sticks down
!           = 3 = Center - text is centered to middle of image
!           = 4 = Margin Left  - align image aligned to left margin
!           = 5 = Margin Right - align image to right margin

  if (win == 0) rfalse;
  if (~~(IsWinText(win))) rfalse;
  if ((pic) && (~~(DoesGraphicExist(pic)))) rfalse;
  if (inlinepos == 1) inlinepos = 0;  ! 1 is unused
  return glk_image_draw(win, pic, inlinepos, 0);
];

!-------------------------------------------------------------------------
! Object Graphics
!-------------------------------------------------------------------------
! DrawableObj Class
!-------------------------------------------------------------------------

! The routines (DrawLocaleObjsTxt(), DrawLocaleObjsWin(),
! DrawInventoryObjsWin()) which use the DrawableObj class are not TOTALLY
! universal (because they rely on the class), but they are certainly
! alterable and adaptable.

! DrawableObj class = a takeable Inform objects that have accompanying
! pictures that can be drawn in text buffer windows. Text buffer windows are
! used because that is the easiest way to put multiple images in one window.

! 1. Drawable objects in the current LOCATION may be drawn:
!    a.) in the main text window along with the game text  ...OR...
!    b.) in another window, separate from the game text
! ..AND/OR..
! 2. Drawable objects in the player's INVENTORY may be drawn in a
!    separate "inventory" window

! As constructed here, the pictures drawn in the inventory window (2.)
! are the same size as those drawn in the main text window (1. a.).
! Those drawn in a window separate from the game text (1. b.), are smaller
! (because there might be numerous objects in the location). But any
! arrangement is possible -- for instance, only one size picture for all.

! Note:  This class makes more sense if you first play "Just a Dream."

!-------------------------------------------------------------------------

class DrawableObj
 with normal_pic 0, ! normal sized pic for   <<Look>>  (main text window)
!                                         && <<Inv>>   (inventory window)
 smaller_pic 0;     ! smaller sized pic for  <<Look>>  (separate window)


! --- DrawLocaleObjs();

! Draws Drawable objects in the current LOCATION in the main text window.

[ DrawLocaleObjs i;
! --
! i

  for (i=child(location):i~=0:i=sibling(i))
      if (i ofclass DrawableObj)
         DrawTextGraphic(gg_mainwin, i.normal_pic);
];

! --- DrawLocaleObjsWin(win);

! Draws Drawable objects in the current LOCATION in a separate window.

! This uses inline positioning for the image (the last parameter passed
! to DrawTextGraphic()). In this case, it aligns the top of the picture with
! top of the text, letting the rest of the picture stick down.

[ DrawLocaleObjsWin win i;
! win = window to use {global variable}
! ---
! i

  if (win == 0) return;
  if (~~(IsWinText(win))) return;
  ClearWindow(win);
  if (location == thedark)
  { PrintTextStyleWin(win, 3, "  Darkness");
    return;
  }
  PrintTextStyleWin(win, 3, "  Location:  ");
  print " "; ! indentation for first pic
  for (i=child(location):i~=0:i=sibling(i))
      if (i ofclass DrawableObj)
         DrawTextGraphic(win, i.smaller_pic, 2);
];

! --- DrawInventoryObjsWin(win);

! Draws Drawable objects in the player's INVENTORY in a separate window.

! This does not attempt to space the pictures in the "inventory" window, which
! could be a major problem if there are numerous Drawable inventory objects.
! (A possible future refinement.) To ensure that the drawn inventory is current,
! this should be called after each Drawable object is taken/dropped.

[ DrawInventoryObjsWin win i;
! win = window to use
! ---
! i

  if (win == 0) return;
  if (~~(IsWinText(win))) return;
  ClearWindow(win);
  if (location == thedark)
  { PrintTextStyleWin(win, 3, "  Darkness");
    return;
  }
  PrintTextStyleWin(win, 3, "  Inventory:  ", 2);
  print "  "; ! indentation for first pic
  for (i=child(player):i~=0:i=sibling(i))
      if (i ofclass DrawableObj)
         DrawTextGraphic(win, i.normal_pic);
];

!---------------------------------------------------------------------------
! Timed Graphics                                               [ Glk event ]
!---------------------------------------------------------------------------

! --- AnimateGraphics(win, curr_pic, first_pic, last_pic, repeat);

! Draws an "animated" graphic. Based on the layout of the resource definition
! file (.res), iblorb creates the Inform include file .bli (what is actually
! included in the game code) which assigns numerical constants to the resources
! in the blorb file. So pictures listed in subsequent order in the .res file,
! will be assigned subsequent numbers in bli. By using a range of first_pic to
! last_pic in the intended animation series, basic animation can be done (just
! how old-fashioned cartoons were "animated" by quickly flipping cards.)
! Thanks to Adam Cadre's Gull for sharing this nifty trick.

! May draw graphics forward or backward (in reverse), or both (if the game
! author calls it from their own routine), and/or loop around from the
! beginning to the end to provide continuous animation. If not set to repeat
! and the last picture in the series is passed to the last_pic argument, it
! will stop itself after doing only one animation. (Note:  As long as the
! necessary coding has been added to HandleGlkEvent() -- see top of file.)

! AnimateGraphics returns the numerical value of the next picture in the
! series. Essentially, animate_count and the picture are the same. (Since all
! Inform objects are numbers, anyway). The return value should be "caught"
! by a global variable that refers to the current picture being used.

! Example:  mywonderful_pic = AnimateGraphics();


Global animate_count = NULL;

[ AnimateGraphics win curr_pic forward first_pic last_pic repeat;
! win       = window to use   {global variable}
! curr_pic  = current picture {global variable}
! forward   = true/false - play animation forward or backward (in reverse)?
! first_pic = first pic in animation series (constant from .res)
! last_pic  = last pic in animation series (constant from .res)
! repeat    = true/flash - animate continuously?

  if (win == 0) return NULL;
  if (~~(IsWinGraphic(win))) return NULL;
  if (~~(SupportsGlkTimer())) return NULL;
  if (animate_count == NULL) return;

  DrawWinGraphic(win, curr_pic);
  if (forward) animate_count++; else animate_count--; ! {global variable}
  if (repeat)
  { if (forward)
    { if (animate_count > last_pic) animate_count = first_pic; }
    else
    { if (animate_count < first_pic) animate_count = last_pic; }
  } else if ((last_pic) && (curr_pic == last_pic)) animate_count = NULL;
  return animate_count;
];

! --- FlashWindowColors(win, color1, color2);

! Flashes between two colors in a window. (Possible future refinement is to
! flash a series of colors in sequence -- using an array.) (Note:  See section
! at the top of this file about programming timed events.)

Global flash_count = NULL;

[ FlashWindowColors win color1 color2;

  if (win == 0) return;
  if (~~(IsWinGraphic(win))) return;
  if (~~(SupportsGlkTimer())) return;
  if (flash_count == NULL) return;

  if (flash_count)
     FloodFillWindow(win, color1);
  else
     FloodFillWindow(win, color2);

  if (flash_count) flash_count = 0; else flash_count = 1;
];

!---------------------------------------------------------------------------
! CURSOR
!---------------------------------------------------------------------------

! Moves the cursor to x, y position. Only applies to text grid windows.

! --- MoveCursor(win, xpos, ypos); **

[ MoveCursor win xpos ypos;
! win  = win to move cursor in
! xpos = x pos at which to position cursor (vertical)
! ypos = y pos at which to position cursor (horizontal)

 return glk_window_move_cursor(win, xpos, ypos);
];

!---------------------------------------------------------------------------
! PLAYER'S INPUT
!---------------------------------------------------------------------------

! --- GetPlayersInput(prompt_str, out_array, out_len);

! Allows the player enter their name (or anything else).

! KeyboardPrimitive() is used by the Glulx Inform library to get the player's
! input at the prompt. The game writer may also use it. By passing it a
! "local" array (instead of using the library's normal input buffer), the
! player's input will not be tokenized into game commands. Note that
! KeyboardPrimitive() also uses a simple character array, with its length in
! the first word, and no null terminator. To be on the safe side, the length
! of the array passed is the same length as the input buffer.

! But, for game purposes, the length of the player's input (first name, last
! name, etc.) should be limited. So the results are copied to a shorter array,
! which is author-defined. That array is what will be used to print out the
! results within the game (see PrintTheArray() below). Its length may be defined
! as a constant. If so, it should be one less than the number assigned to
! the array (since Inform arrays start at zero). And the array should also be
! a simple character array, like the one used by KeyboardPrimitive().

! Example:

! Constant FIRST_NAME_LEN = 15;    author-defined array
! Array first_name ->16;           length of author-defined array

! Array used by GetPlayersInput:

Constant PLAYERS_INPUT_LEN = 120;
Array players_input ->121;

[ GetPlayersInput prompt_str out_array out_len;
! prompt_tr - string to print to prompt player
! out_array - author-defined array
! out_len   - length of author-defined array
!             (may use constant)

  print (string) prompt_str; print "  ";
  ClearArray(players_input);
  KeyboardPrimitive(players_input);
  CopyInArrayToOutArray(players_input, out_array, out_len);
];

! ---ValidatePlayersInput(out_array);

! Validates the player's input (not required). If used, it should be placed
! in a loop with GetPlayersInput, so if the player answers "incorrect", then
! his/her input may be entered again.

[ ValidatePlayersInput out_array yesno;
! out_array - author-defined array
! ------
! yesno     - result of YesorNo function

  print "Is ";
  PrintTheArray(out_array);
  print " correct?  ";
  yesno = YesorNo();
  if (~~(yesno))
     ClearArray(out_array);
  return yesno;
];

! Brendan Barnwell was helpful in developing the routine, PrintTheArray,
! to print out the player input gathered with KeyboardPrimitive(). The array
! must be offset by WORDSIZE (since this is Glulx add-on library, that is 4).

! The game author may want to use PrintTheArray to print out the results of
! the player's input in the game. It should be passed the author-defined array.

[PrintTheArray array len i ch;
  len = array-->0;
  for (i=0 : i<=(len-1) : i++)
  { ch = array->(i+WORDSIZE);
    if (ch) print (char) ch;
  }
];

! Both of the following routines are for player's input and are not really meant
! to be called by the game author. Although nothing will "break", if they are.

[ CopyInArrayToOutArray in_array out_array out_len i;
  for (i=0: i<=(out_len-1): i++)
  { if (in_array->(i+WORDSIZE) == 0) break;
    out_array->(i+WORDSIZE) = in_array->(i+WORDSIZE);
  }
  if (i > out_len) i = out_len;
  out_array-->0 = i;
];

[ ClearArray array len i;
  len = array-->0;
  array->0 = 0;
  for (i=0: i<=(len-1): i++)
      array->(i+WORDSIZE) = 0;
  array-->0 = 0;
];

!---------------------------------------------------------------------------
! TEXT
!---------------------------------------------------------------------------

! These text style routines currently only affect text buffer windows,
! because text grid windows are limited to fixed-width fonts and graphics
! windows can't print text. However, they could be expanded to include text
! grid windows by adding another argument (a possible future refinement).

!-------------------------------------------------------------------------
! Setting Text Styles
!-------------------------------------------------------------------------

! Text Styles must be set before the window they are to be used in is opened.
! So they can only be set for a type of window, not an individual window.

! Since the style categories are the same for all the following routines,
! they are only listed once in the first routine, SetTextStyleColor. Also,
! because the function names are fairly descriptive in and of themselves,
! no explanation is given for each one.

! Sets "suggests" style attributes for a specific style category.

! --- SetTextStyleColor(style_cat, color); *

[ SetTextStyleColor style_cat color;
!                  style_cat = infglk constant/prefix style_ (style_Normal)
! style_cat =  0 = Normal       - normal text
! style_cat =  1 = Emphasized   - usually bold (may be italic)
! style_cat =  2 = Preformatted - fixed-width font - for maps/diagrams
! style_cat =  3 = Header       - large text - for titles like game name
! style_cat =  4 = Subheader    - smaller than Header - subtitles like room name
! style_cat =  5 = Alert        - warns of dangerous condition
! style_cat =  6 = Note         - interesting condition like score notification
! style_cat =  7 = BlockQuote   - may be indented - quotations, etc. (like HTML)
! style_cat =  8 = Input        - player's input
! style_cat =  9 = User1        - defined by game programmer
! style_cat = 10 = User2        - ditto
! color     =  color (number) - due to HTML, hexadecimal notation is easy to use

 return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_TextColor,
                          color);
];

! --- SetTextStyleBackColor(style_cat, color); *

[ SetTextStyleBackColor style_cat color;
! style_cat =  0 - 10
! color     =  color (number) - due to HTML, hexadecimal notation is easy to use

 return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_BackColor,
                          color);
];

! --- SetTextStyleReverseColor(style_cat, onoff); *

[ SetTextStyleReverseColor style_cat onoff;
! style_cat =  0 - 10
! onoff     =  true  = reverse fore and back colors
!              false = no, normal colors

 return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_ReverseColor,
                          onoff);
];

! --- SetTextStyleSize(style_cat, size); *

[ SetTextStyleSize style_cat size;
! style_cat  = 0 - 10
! size       = how much to increase/decrease size
!               0 = default font size used by interpreter
!              +# = positive number increases font size (+1, similar to HTML)
!              -# = negative number decreases font size (-1)
!                   increase/increase in point size may not be consistent
!                   (example:  +1 = 8 to 10 pt, because font has no 9 pt)

  return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_Size, size);
];

! --- SetTextStyleWeight(style_cat, weight); *

[ SetTextStyleWeight style_cat weight;
! style_cat =  0 - 10
! weight    = -1 = light weight
!              0 = normal
!              1 = bold

  return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_Weight,
                           weight);
];

! The following three routines are included because it is easier to remember
! SetTextStyleBold than SetTextStyleWeight(1).

! --- SetTextStyleLight(style_cat); *

[ SetTextStyleLight style_cat;
  return SetTextStyleWeight(style_cat, -1);
];

! --- SetTextStyleNormal(style_cat); *

[ SetTextStyleNormal style_cat;
  return SetTextStyleWeight(style_cat, 0);
];

! --- SetTextStyleBold(style_cat); *

[ SetTextStyleBold style_cat;
  return SetTextStyleWeight(style_cat, 1);
];

! --- SetTextStyleItalic(style_cat, onoff); *

[ SetTextStyleItalic style_cat onoff;
! style_cat =  0 - 10
! onoff     =  1 = turn on italics
!              0 = no, turn off italics

 return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_Oblique,
                          onoff);
];

! --- SetTextStyleProporFixed(style_cat); *

[ SetTextStyleProporFixed style_cat which;
! style_cat = 0
! which     = 1 = proportional
!             0 = fixed-width

  return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_Proportional,
                           which);
];

! The following two routines are included because it is easier to remember
! SetTextStyleProportional than SetTextStyleProporFixed(1).

! --- SetTextStyleProportional(style_cat); *

[ SetTextStyleProportional style_cat;
  return SetTextStyleProporFixed(style_cat, 1);
];

! --- SetTextStyleFixed(style_cat); *

[ SetTextStyleFixed style_cat;
  return SetTextStyleProporFixed(style_cat, 0);
];

! --- SetTextStyleJustified(style_cat, just_side); *

[ SetTextStyleJustified style_cat just_side;
!               infglk constant with prefix stylehint_just
! style_cat  =  0 - 10
! just_side  =  0 = FlushLeft
!            =  1 = LeftRight - full justification
!            =  2 = Centered
!            =  3 = RightFlush

 return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_Justification,
                          just_side);
];

!---------------------------------------------------------------------------
! Testing Text Styles
!---------------------------------------------------------------------------

! Tests a style category in a specific window to see if it has a specific
! style attribute. Unlike setting attributes, style categories for individual
! windows can be tested, because once windows are open, attributes for the
! same category can differ from window to window.

! (Note:  A return value of zero may be misleading. In many cases 0 may mean
! that a category has one attribute or the other set, TestStyleProporFixed()
! = 1 = proportional, 0 = fixed. But if the interpreter can't determine if
! that style category has that style attribute, then it will also return 0.)


! --- TestTextStyleColor(win, style_cat);

[ TestTextStyleColor win style_cat result;
!             style_cat = infglk constant/prefix style_ (style_Normal)
! win       = window to test
! style_cat =  0 = Normal       - normal text
! style_cat =  1 = Emphasized   - usually bold (may be italic)
! style_cat =  2 = Preformatted - fixed-width font - for maps/diagrams
! style_cat =  3 = Header       - large text - titles like game name
! style_cat =  4 = Subheader    - smaller than Header - subtitles like room name
! style_cat =  5 = Alert        - warns of dangerous condition
! style_cat =  6 = Note         - interesting condition like score notification
! style_cat =  7 = BlockQuote   - may be indented - quotations, etc. (like HTML)
! style_cat =  8 = Input        - player's input
! style_cat =  9 = User1        - defined by game programmer
! style_cat = 10 = User2        - ditto
! --------
! result      color

  glk_style_measure(win, style_cat, stylehint_TextColor, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

! --- TestTextStyleBackColor(win, style_cat);

[ TestTextStyleBackColor win style_cat result;
! win       = window to test
! style_cat =  0 - 10
! ---------
! result      color

  glk_style_measure(win, style_cat, stylehint_BackColor, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

! --- TestTextStyleReverseColor(win, style_cat);

[ TestTextStyleReverseColor win style_cat result;
! win       = window to test
! style_cat =  0 - 10
! ---------
! result      true/false

 glk_style_measure(win, style_cat, stylehint_ReverseColor, gg_arguments);
 result = gg_arguments-->0;
 return result;
];

! --- TestTextStyleSize(win, style_cat);

[ TestTextStyleSize win style_cat result;
! win       = window to test
! style_cat = 0 - 10
! ---------
! result      size

  glk_style_measure(win, style_cat, stylehint_Size, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

! --- TestTextStyleWeight(win, style_cat);

[ TestTextStyleWeight win style_cat result;
! win       = window to test
! style_cat = 0 - 10
! ---------
! result      -1 = light, 0 = normal, 1 = bold

  glk_style_measure(win, style_cat, stylehint_Weight, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

! The following three routines are included because it is easier to remember
! TestTextStyleBold than TestTextStyleWeight() = 1, equals bold.

! --- TestTextStyleLight(win style_cat);

[ TestTextStyleLight win style_cat result;
! win       = window to test
! style_cat = 0 - 10

  result = TestTextStyleWeight(win, style_cat);
  if (result == 0) rtrue;
  rfalse;
];

! --- TestTextStyleNormal(win, style_cat);

[ TestTextStyleNormal win style_cat result;
! win       = window to test
! style_cat = 0 - 10

  result = TestTextStyleWeight(win, style_cat);
  if (result == 0) rtrue;
  rfalse;
];

! --- TestTextStyleBold(win style_cat);

[ TestTextStyleBold win style_cat result;
! win       = window to test
! style_cat = 0 - 10

  result = TestTextStyleWeight(win, style_cat);
  if (result == 1) rtrue;
  rfalse;
];

! --- TestTextStyleItalic(win, style_cat);

[ TestTextStyleItalic win style_cat result;
! win       = window to test
! style_cat = 0 - 10
! ---------
! result    true/false

  glk_style_measure(win, style_cat, stylehint_Oblique, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

! --- TestTextStyleProporFixed(win, style_cat);

[ TestTextStyleProporFixed win style_cat result;
! win       = window to test
! style_cat = 0 - 10
! ---------
! result      1 = proportional, 0 = fixed

  glk_style_measure(win, style_cat, stylehint_Proportional, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

! The following two routines are included because it is easier to remember
! TestTextStyleFixed than TestTextStyleProporFixed() = 0, equals fixed.

! --- TestTextStyleFixed(win, style_cat);

[ TestTextStyleFixed win style_cat result;
! win       = window to test
! style_cat = 0 - 10

 result = TestTextStyleProporFixed(win, style_cat);
 if (result == 0) rtrue;
 rfalse;
];

! --- TestTextStyleProportional(win, style_cat);

[ TestTextStyleProportional win style_cat result;
! win       = window to test
! style_cat = 0 - 10

 result = TestTextStyleProporFixed(win, style_cat);
 if (result == 1) rtrue;
 rfalse;
];

! --- TestTextStyleJustified(win, style_cat);

[ TestTextStyleJustified win style_cat result;
!              result = infglk constant/prefix stylehint_ (stylehint_FlushLeft)
! win       = window to test
! style_cat =  0 - 10
! ---------
! result       0 = FlushLeft
!              1 = LeftRight - full justification
!              2 = Centered
!              3 = RightFlush

  glk_style_measure(win, style_cat, stylehint_Justification, gg_arguments);
  result = gg_arguments-->0;
  return result;
];

!---------------------------------------------------------------------------
! Clearing Text Styles
!---------------------------------------------------------------------------

! These routines clear a specific style attribute from a specific style
! category. Again, they only do this for one TYPE of window, not an individual
! window. And, again, clearing must be done before that window is opened.


! --- ClearTextStyleColor(style_cat); *

[ ClearTextStyleColor style_cat;
!                  style_cat = infglk constant/prefix style_ (style_Normal)
! style_cat =  0 = Normal       - normal text
! style_cat =  1 = Emphasized   - usually bold (may be italic)
! style_cat =  2 = Preformatted - fixed-width font - for maps/diagrams
! style_cat =  3 = Header       - large text - titles like game name
! style_cat =  4 = Subheader    - smaller than Header - subtitles like room name
! style_cat =  5 = Alert        - warns of dangerous condition
! style_cat =  6 = Note         - interesting condition like score notification
! style_cat =  7 = BlockQuote   - may be indented - quotations, etc. (like HTML)
! style_cat =  8 = Input        - player's input
! style_cat =  9 = User1        - defined by game programmer
! style_cat = 10 = User2        - ditto

 return glk_stylehint_set(wintype_TextBuffer, style_cat, stylehint_TextColor);
];

! --- ClearTextStyleBackColor(style_cat); *

[ ClearTextStyleBackColor style_cat;
 return glk_stylehint_clear(wintype_TextBuffer, style_cat, stylehint_BackColor);
];

! --- ClearTextStyleReverseColor(style_cat); *

[ ClearTextStyleReverseColor style_cat;
 return glk_stylehint_clear(wintype_TextBuffer, style_cat,
                            stylehint_ReverseColor);
];

! --- ClearTextStyleSize(style_cat); *

[ ClearTextStyleSize style_cat;
  return glk_stylehint_clear(wintype_TextBuffer, style_cat, stylehint_Size);
];

! --- ClearTextStyleWeight(style_cat); *

[ ClearTextStyleWeight style_cat;
  return glk_stylehint_clear(wintype_TextBuffer, style_cat, stylehint_Weight);
];

! --- ClearTextStyleItalic(style_cat); *

[ ClearTextStyleItalic style_cat;
 return glk_stylehint_clear(wintype_TextBuffer, style_cat, stylehint_Oblique);
];

! --- ClearTextStyleProporFixed(style_cat); *
[ ClearTextStyleProporFixed style_cat;
  return glk_stylehint_clear(wintype_TextBuffer, style_cat,
                             stylehint_Proportional);
];

! --- ClearTextStyleJustified(style_cat); *

[ ClearTextStyleJustified style_cat;
  return glk_stylehint_clear(wintype_TextBuffer, style_cat,
                             stylehint_Justification);
];

!---------------------------------------------------------------------------
! Printing with/without Text Styles
!---------------------------------------------------------------------------

! Using a style category, selects a text style for subsequent printing in
! the current window. Uses the style category as is -- with preset
! attributes and/or as it has been changed by the player -- or uses a style
! category for which some style attributes have previously been suggested.

! --- SelectTextStyle(style_cat); *

[ SelectTextStyle style_cat;
!                  style_cat = infglk constant/prefix style_ (style_Normal)
! style_cat =  0 = Normal       - normal text
! style_cat =  1 = Emphasized   - usually bold (maybe italic)
! style_cat =  2 = Preformatted - fixed-width font (for maps/diagrams)
! style_cat =  3 = Header       - large text (titles)
! style_cat =  4 = Subheader    - smaller than Header (subtitles)
! style_cat =  5 = Alert        - warns of dangerous condition
! style_cat =  6 = Note         - interesting condition, like score notification
! style_cat =  7 = BlockQuote   - similar to HTML, quotations, etc.
! style_cat =  8 = Input        - player's input
! style_cat =  9 = User1        - defined by game programmer
! style_cat = 10 = User2        - ditto

  return glk_set_style(style_cat);
];

! --- PrintTextStyle(style_cat, str);

! Prints to the main text window. Prints a string using a specific text style
! (style category). Printing stays on the same line as previous text.

[ PrintTextStyle style_cat str;
! style_cat = 0 - 10
! str       = string to print

  SelectTextStyle(style_cat); ! switch to passed style CATEGORY
  print (string) str;         ! print passed string
  SelectTextStyle(0);         ! switch back to normal category (normal text)
];

! --- PrintTextStyleWin(win, style_cat, str, line_space);

! Prints to a window other than the main window. Prints a string using a
! specific text style (style category). May have line spacing, or stay on
! the same line as previous text.

[ PrintTextStyleWin win style_cat str line_space;
!              style_cat = infglk constant/prefix style_ (style_Normal)
! win        = window to print to
! style_cat  = 0 - 10
! str        = string to print
! line_space =  0 = no line spacing - print on current line
!               # = number of lines to space after printing

  SwitchWindow(win);          ! make passed window the current window
  SelectTextStyle(style_cat); ! switch to passed style CATEGORY
  print (string) str;         ! print passed string
  while(line_space > 0)       ! add line spacing
  { new_line; line_space--; }
  SelectTextStyle(0);         ! switch back to normal category for current win
  SwitchWindow(gg_mainwin);   ! switch back to main text buffer window
];

! Since the style category, Emphasized, means exactly what it says, this
! should boldface text. However, if the player has changed it, it may not.

[ boldface str;
  PrintTextStyle(1, str);
];

!---------------------------------------------------------------------------
! Timed Text                                                     [Glk event]
!---------------------------------------------------------------------------

! Creates a "ticker tape" effect by printing a string one character at a time
! at the speed set when starting the Glk timer.

Constant TICKERTAPE_LEN 66;
Array ticker_array ->67;

Global ticker_count = NULL;

! --- SetTickerTapeStr(str);

! Sets the string to be printed ticker-tape-style. (This can crash the game
! if the author uses a string that exceeds the length of the ticker_array.)
! Only called once per special effect, right before calling TickerTape().

[ SetTickerTapeStr str len;
! str  - literal string ("enclosed in quotes")
! ----
! len  - length of string

  len = PrintAnyToArray(ticker_array+WORDSIZE,
                        TICKERTAPE_LEN-1, str);
  ticker_array-->0 = len;
];

! --- TickerTape();

! Does the actual ticker tape special effect. Stops itself when done.
! This routine does not need its counter reset to NULL to stop it, unless
! the player can somehow interrupt its execution. (Note:  See the section at
! the top of this file on programming timed events.)

[ TickerTape len ch;
  len = ticker_array-->0;
  if (ticker_count > len)
  {  ticker_count = NULL;
     StopGlkTimer();
     return;
  }
  ch = ticker_array ->((ticker_count-1)+WORDSIZE);
  if (ch) print (char) ch;
  ticker_count++;
];

!---------------------------------------------------------------------------
! SOUND                                                        [Glk objects]
!---------------------------------------------------------------------------

! --- CreateChannel(rockid);

! Creates a sound channel to play a sound in.

[ CreateChannel rockid;
! rockid  = rock number (global constant)
! ------
! returns   channel created

  return glk_schannel_create(rockid);
];

! --- PlaySound(channel, snd, repeat, times, notify); **

! Plays a sound in a sound channel. If the sound (snd) passed is zero, will
! stop the sound currently playing in that channel.

[ PlaySound channel snd repeat times notify;
! channel  = channel to use
! snd      = current sound {global variable}
! repeat   = 1 = play continuously
!            0 = no, don't
! times    = # = if not continuous, how many times to play
! notify   = 1 = use sound notification? (true/false)
!                (when sound finishes playing, HandleGlkEvent() is notified)
! result

  if (channel == 0) rfalse;
  if (snd == 0) return glk_schannel_stop(channel);
  else
  {  if ((repeat == 0) && (times == 0))
        return glk_schannel_play(channel, snd);
     else
     { if (repeat)
          return glk_schannel_play_ext(channel, snd, -1, notify);
       else
          return glk_schannel_play_ext(channel, snd, times, notify);
     }
  }
];

! --- GetNextChannel(rockid, arg_ptr);

! Using the sound channel class, loops through the existing sound channels
! -- usually only be called in HandleGlkEvent(). If zero is passed to the
! first argument, this returns the first channel's rock id.

[ GetNextChannel rockid arg_array;
! rockid    = rock number (global constant)
! arg_array = gg_arguments
! -------
! returns     rock constant

  return glk_schannel_iterate(rockid, arg_array);
];

! --- DestroyChannel(channel); *

! Removes a sound channel (not a sound file) from memory -- freeing resources.

[ DestroyChannel channel;
! channel = channel to get rid of {global variable}

  if (channel) return glk_schannel_destroy(channel);
];

!---------------------------------------------------------------------------
! FILES
!---------------------------------------------------------------------------

!---------------------------------------------------------------------------
! File references                                              [Glk objects]
!---------------------------------------------------------------------------

! I was unable to get DoesFileExist() and DeleteFile() to work on the Windows
! Glulxe interpreter. They may not be implemented yet in the Windows Glk
! library, or there may be some other problem.

! --- DoesFileExist(file_ref);

! Using a file reference, tests if a file exists on disk.

[ DoesFileExist file_ref;
! file_ref = file reference (name and type of file)
! --------
! returns    true/false

  return glk_fileref_does_file_exist(file_ref);
];

! --- DeleteFile(file_ref);  **

! Using a file reference, deletes a disk file.

[ DeleteFile file_ref;
! file_ref = file reference (name and type of file)

  return glk_fileref_delete_file(file_ref);
];

! --- CreateFileRef(file_name, usage, text_file, rockid);

! Creates a file reference with the given name and file type. The interpreter
! may add on an extension (based on the usage), or not. A file reference is
! essentially a pointer to an external file.

! (Note:  If the file will be a binary file, fileusage_Binary doesn't need to
! be added because that infglk constant = 0. However, text files may be written
! in either text or binary mode, even though they are text. ChangeAnyToCString()
! limits the length of the file name and also turns it into a Glulx string.)

[ CreateFileRef file_name usage text_file rockid shortname;
!               usage = infglk constant/prefix fileusage_ (fileusage_Data)
! file_name   = file name to use
! usage       = 0 = Data - miscellaneous
!             = 1 = SaveGame
!             = 2 = Transcript
!             = 3 = InputRecord
! text_file   = 1 = text file
!             = 0 = no, binary file
! rockid      = rock number (global constant)
! ------
! shortname   = file name truncated to eight letters
! returns       file reference created

  shortname = ChangeAnyToCString(file_name, 8);
  if (text_file) usage = (usage + fileusage_TextMode);
  return glk_fileref_create_by_name(usage, shortname, rockid);
];

! CreateFileRefByPrompt(usage, text_file, mode, rockid);

! Normally the write mode is only used with file streams, not file references.
! But because this prompts the player to provide the file name, the read/write
! mode is used to determine whether to ask the player:  1.) to load an existing
! file (Read, Read/Write, Write/Append); or 2.) or save a new file (Write).

[ CreateFileRefByPrompt usage text_file mode rockid;
!                usage = infglk constant/prefix fileusage_ (fileusage_Data)
!                mode  = infglk constant/prefix filemode_ (filemode_Write)
! usage        = 0 = Data - miscellaneous
!              = 1 = SaveGame
!              = 2 = Transcript
!              = 3 = InputRecord
! text_file    = 1 = text file
!              = 0 = no, binary file
! mode         = 1 = Write
!                2 = Read
!                3 = ReadWrite
!                4 = is unused
!                5 = WriteAppend
! rockid       = rock number (global constant)
! ------
! returns        file reference created

  if (text_file) usage = (usage + fileusage_TextMode);
  return glk_fileref_create_by_prompt(usage, mode, rockid);
];

! --- DestroyFileRef(file_ref); *

! Removes a file reference (not a file) from memory -- freeing resources.

[ DestroyFileRef file_ref;
! file_ref = file reference to get rid of {global variable}

  if (file_ref) return glk_fileref_destroy(file_ref);
];

!---------------------------------------------------------------------------
! File Streams                                                 [Glk objects]
!---------------------------------------------------------------------------

! --- OpenFileStream(file_ref, mode, rockid);

! Using a file reference created previously, writes to a disk file. When a
! file stream is first opened, a disk file is actually created on disk.
! When an existing disk file is opened, information can be added to the file.

! If opened with Write, the file's read/write marker is set at the beginning
! of the file, creating a new file or erasing an existing one. If opened with
! Append, the file's read/write marker is set to the end of the file, allowing
! information to be appended to the end.


[ OpenFileStream file_ref mode rockid;
!               mode = infglk constant/prefix filemode_ (filemode_Write)
! file_ref    = file reference - created previously {global variable}
! mode        = 1 = Write
!               2 = Read
!               3 = ReadWrite
!               4 = is unused
!               5 = WriteAppend
! rockid      = rock number (global constant)
! ------
! returns       file stream created

  if (mode == 4) return 0; ! 4 is unused
  if (file_ref) return glk_stream_open_file(file_ref, mode, rockid);
];

! --- EchoWinToFile(win, file_strm); **

! Using a file stream created previously, echos a window stream to a file
! stream that was created previously. (Note:  Every window has a built-in
! output stream associated with it).

[ EchoWinToFile win file_strm;
! win        = window (stream) to echo  {global variable}
! file_strm  = file stream to echo to   {global variable}

  if (file_strm) return glk_window_set_echo_stream(win, file_strm);
];

! --- CloseFileStream(file_strm); **

! Closing a file stream, closes the disk file. Since a file reference exists
! independently from its associated file stream, it can be destroyed
! while the stream is still open without destroying the stream or disk file.

[ CloseFileStream file_strm;
! file_strm = file stream {global variable}

  if (file_strm) return glk_stream_close(file_strm);
];


