HTTP and HTML

This chapter covers the aspects of HTML and HTTP that relate to writing games for use with the cgijacl interpreters, including the JACL commands that assist with the output of HTML. If you are not familiar with HTML, it is highly recommended that you read at least a basic tutorial before starting to write your first web-based game.
The jacl interpreter will consider all the HTML-specific commands as errors so it is important to either test the value of constant interpreter (either GLK or CGI) to be sure the game is being played with a web version of JACL or use these commands from a function like +header or +footer that is only executed by the cgijacl interpreter.

Document Structure

A complete HTML page must be returned by your game in response to each of the player's moves. To assist with this, if you do not define your own custom header and footer functions (+header and +footer), the cgijacl interpreter will insert template headers and footers to mimic a standard interactive fiction interface. If a +header function is supplied, it is the very first thing to be executed. The default header looks like this:

 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"> 
 
<html><head> 
   <title>game_title</title> 
   <script language="JavaScript"> 
   <!--
   function userCommand() {
      var xhReq = createXMLHttpRequest();
      if(xhReq == null) { return true; }
      var user_id = document.JACLGameForm.user_id.value;
      var command = document.JACLGameForm.command.value;
      xhReq.open("GET", "?user_id="+user_id+"&command="+command+"&ajax=true", false);
      xhReq.send(null);
      var serverResponse = xhReq.responseText;
      var maintext = document.getElementById("maintext");
      maintext.innerHTML += "<br><b>>" + command + "</b><br>" + serverResponse;
      maintext.scrollTop = maintext.scrollHeight;
      document.JACLGameForm.command.value = "";
      return false; }
    function putFocus(formInst, elementInst) {
      if (document.forms.length > 0) {
          document.forms[formInst].elements[elementInst].focus(); }}
    function createXMLHttpRequest() {
       try { return new XMLHttpRequest(); } catch(e) {}
       try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) {}
       return null; }
    -->
    </script> 
    <style> <!--
       #footer { 
           position:absolute;
           bottom:0;
           left:0;
           right:0;
           display:block;
           font-family: Verdana, Arial, Sanserif;
           padding-top: 10px;
           padding-bottom: 10px;
           padding-left: 20px;
           background: #dddddd; }
       #main {
           position:absolute;
           margin-left:10;
           margin-right:10;
           left:0;
           top:0px;
           right:0;
           overflow:auto; }
       div.maintext {
            font-family: Verdana, Arial, Sanserif;
            padding-top: 20px;
            padding-bottom: 20px;
            padding-left: 50px;
            padding-right: 50px;
            font-size: 12pt;
            overflow: auto; }
    --> </style> 
    </head><body onLoad="putFocus(0, 0);"> 
    <div id="main"> 
    <div id="maintext" class="maintext"> 

This header sets the DOCTYPE for the returned HTML document, sets the title of the browser window to the name of the game, provides some Javascript functions to set the input focus to the command prompt and make the Ajax call to the server when a command is entered. It also outputs the CSS styles required and opens a DIV for the game's output to appear in.

The body of the maintext DIV is then produced by your game.
If you do provide your own custom +header function, be aware that you can't display values that are modified by the player's command. This is because a full page is generated in order, top to bottom. For example, if the header was to display the title of the current location, when the player moved the header would be displayed and would show the current location. The movement command would then be processed to move the player and then the footer displayed. As you can see, this would result in the header showing the location the player was in before the command took place. The footer does not have this limitation.

The function +footer is the very last function to be executed after a player's move has been processed. If one is not supplied the following default output is inserted by the interpreter:

   </div> 
   <div id="footer" class="footer"> 
      <p><b>></b> <input type=text style='border-style:none;' size=60 name="command"></center> 
      <input type=hidden name="user_id" value="xxxxxx-xxx"> 
   </div> 
   </form>
   </body>
</html> 

There are two significant aspects to this footer. The first is that it contains the input field where the player will type their commands. This input field must have the name command as this is where the interpreter will look for the player's command when the form is submitted. The second is the hidden input that contains the user_id for the player.
An alternative to using the header and footer built into interpreter or coding your own from scratch is to use the webinterface.library.

Linebreaks

In order to avoid the need to fill a standard interactive fiction game or the standard library with HTML tags the caret newline character (^) is output by the cgijacl interpreters as <br> followed by a newline. This behaviour can be turned off by setting the JACL variable linebreaks to false. This is usually desirable in sections such as the header and footer where code is only executed by the web-based interpreters and can therefore contain any amount of custom HTML you require.

The Player's User ID

HTTP is a stateless protocol. This means that each request received by the web server is completely independent of any that came before it. The cgijacl interpreters, however, need to know which player each request has come from, and more specifically, what state the game world was in at the end of their previous move. For this reason, a unique user ID must be sent with each of the player's commands. Failure to do so at any time will result in the game restarting from the beginning and a new user ID being assigned automatically.

To propagate the player's user ID, it must be passed as the HTML parameter user_id. This is done by including it as a hidden field in any forms, or as a URL parameter in any hyperlinks. The token $user_id may be used in any write statement to output the automatically generated ID. Below are two examples of its use:

write "<a href=~" $url "?user_id=" $user_id 
write "&command=look~>Look</a>"
    
write "<input type=~hidden~ name=~user_id~ "
write "value=~" $user_id "~>"

In the first set of write statements, the token $user_id is added directly to the end of the URL being linked to as the value of the HTTP parameter user_id. Also used is the token $url. This token displays the URL of the game, such as /jacl/game.fcgi.
When playing games with the cgijacl interpreter, there is no URL to the game. It is wise, however, to always construct links using the $url token so your game will also work when played with the fcgijacl interpreter.

The second set of write statements output a hidden form field. This field should be used as part of an HTML form when the player issues their commands by submitting this form.

When using an HTML form, there are two simpler methods of including the player's user ID. The first is the command hidden. This command outputs a line exactly like the input tag above. Below is an example of its use:

write "<form>"
write "<input type=~text~ name=~command~>"
hidden ;adds the player's user ID as a hidden form field.
write "</form>"

As you can see, there is nothing difficult about using the hidden command. The second related command is prompt. This command outputs a text input box and the player's user ID. Using prompt, the above code snippet would look like this:

write "<form>"
prompt ;adds a text field with the name 'command' and
       ;the player's user ID as a hidden field.
write "</form>"

As you can see, there is even less to using the prompt command.
If you are running a game using the fcgijacl interpreter under a full webserver, the value of the environment variable REMOTE_USER will be used if present instead of creating a randomly generated user ID. This allows a JACL game to be protected by the webserver and the player to receive the same user ID each time they log in and play.

The Player's Commands

The player's commands are passed to the interpreter via three parameters: verb, noun and command. If the parameters verb and noun are passed, noun is concatenated on the end of verb and the result is processed as a single command. This is useful when writing point-and-click adventures that use a combination of list boxes and submit buttons to form verb-noun commands. If the parameter command is passed, it is processed as a complete command. This is useful for traditional text-based adventures.

It is also possible to use both methods of passing the player's moves. When a request is received, the interpreter will first test the value of verb. If this is empty, the value of command is used as the player's move. If verb does contain a value, the value of noun will be concatenated to it, and the move will be processed, ignoring any value that command may or may not have.

Ajax Requests

Using standard Javascript techniques (see the default header or webinterface.library for an example), it is possible to request the JACL interpreter to process a command without refreshing the whole browser window. Once the command has been processed Javascript must be used once again to manually insert any output returned into the appropriate HTML element. When performing these types of requests, there are two special URL parameters to be aware of.

The first is the ajax parameter. If this parameter has the value true the header and footer functions will not be called, leaving the interpreter to return only the output produced directly by the command being processed.

The second is the rpc parameter. This parameter allows the game author to specify a function to execute as opposed to the usual process of interpreting a command entered by the player. Four values are handled specially:

Most ajax requests also carry a status_cols parameter measuring how many monospace characters fit across the rendered status bar. The interpreter copies this into status_window_width before processing the request so cursor-positioning opcodes line up with the actual viewport. Game source must not write to status_window_width directly.

Any other value falls through to the global function +rpc with the requested name (prefixed with a plus sign) stored in rpc_function_name. This gives you a single dispatch point for arbitrary background calls while keeping the set of directly-callable function names under your control — the request never gets to invoke an arbitrary JACL function by name.
The +ajax function serves no predetermined purpose, it is simply a function that can be called by the game author to handle any special-case situations that arise. +rpc works the same way but covers everything else: read rpc_function_name inside +rpc to decide what to do.

The BUTTON Command

The button command is used to create a button that will submit an HTML form. It must be followed by a single parameter that indicates what command this button should issue. Behind the scenes this command creates an input tag of the type submit and sets the value of the HTML parameter verb to the specified command. Below is an example of its use:

button Look
button "Read Map"

The command specified for each button will appear as text on the button. If a command contains any spaces, the command must be enclosed in double quotes as usual.

It is possible to use a second HTML control, such as a list box, in conjunction with a submit button to create verb-noun commands. See the control command below for more information.

The HYPERLINK and HYPERLINKNE Commands

The command hyperlink is a simple method of creating a hyperlink that issues a command for the player. It can accept either two or three parameters. The first is the visible text to be displayed. The second parameter is the command to be issued when the link is clicked. The third, optional, parameter is the name of a CSS class that should be applied to the link. Below are some sample hyperlinks:

hyperlink East east 
hyperlink "Take All" "take+all" small
hyperlink "Where am I?" look big

With the third hyperlink, the text Where am I? will appear on the screen with all the styles specified the CSS class big applied. When clicked, it will pass the value look to a parameter named command. This, in effect, issues a look command on the player's behalf.
When using the hyperlink command, any spaces in the second parameter (the command to be executed), must be replaced with plus signs. This is due to the fact that the command is passed as part of the URL sent to the web browser, and spaces are not valid in a URL.

The hyperlinkNE command works in the same way as the hyperlink command except that it does not URL-encode the command parameter. This can be useful when the command string has already been encoded or when constructing complex URLs manually.

When manually creating special-purpose hyperlinks, the {+name} macro is very useful. For more information, see the chapter on Screen Display.

The CONTROL Command

The control command is also used to create a hyperlink. With the control command, however, the first parameter is the name of an image to display instead of some plain text. As the control command displays an image, no third parameter indicating a CSS style is allowed. Below is an example of a control command:

control "/images/look.png" look 

This command will create a hyperlink that issues the command look. The hyperlink will appear on the screen as the image look.png.

The OPTION Command

The option command is used to include an object as a part of a <select> list box. A list box allows the user to select one of the objects from the list then submit the form. The selected object is then sent to the server in the form of a name and value pair. This is most useful when the select tag used to create the list box sets the object selected to the parameter noun as the cgijacl interpreters will automatically look for this parameter when constructing a move to process. By default, the option command will set the value returned to all of the specified object's names. The object for each option is specified by following the option command with an object label or pointer. The visible text for the option is the object's short description. For example:

<form>
   write "<select name=~noun~>"
   loop
      if noun3 is *present
         option noun3
      endif
   endloop
   write "</select>"

   button Look
   button Use
   button Take
   button Drop		
</form>

The above code creates a list box with a parameter name of noun and adds an entry for each object that is in the player's current location. If an object is selected before the form is submitted by clicking one of the four buttons, the parameter noun will have its value set to all the names of the object the player selected and the parameter verb will have its value set to the text on the button that was clicked. These two parameter would then be concatenated (verb + noun) and processed by the interpreter as the player's move.

It is also possible to specify that an option command should pass the object's index as opposed to its names. This is done by following the object pointer or label with the word index. This is only of use if a parameter statement has been defined to accept the parameter name specified in the select tag. Below is an example of this:

parameter destination SOME_VARIABLE

<form>
   write "<select name=~destination~>"
   loop
      if noun3 is *present
         option noun3 index
      endif
   endloop
   write "</select>"
   hidden
   button Jump
</form>

When this form is submitted, the cgijacl interpreter will look for the parameter destination, as it has been defined using a parameter statement. This parameter will contain the internal number of the object that was selected and this number will be copied into the location specified by the parameter statement. In this case, a variable called SOME_VARIABLE. For more information see the section on parameters.

The GETENV Command

The getenv command is used to retrieve the value of an environment variable and store it in a JACL string variable. This is primarily useful for reading CGI environment variables such as HTTP headers and request parameters. A getenv command must be of the following format:

getenv StringVariable EnvironmentVariableName

The first parameter is the JACL string variable to store the value in. The second parameter is the name of the environment variable to read. If the specified environment variable does not exist, the string variable will be set to an empty string. Below is an example of its use:

string current_user

{+header
getenv current_user "REMOTE_USER"
write "<p>Welcome, " current_user "!</p>"
}

The IMAGE Command

Under the web interpreters, the image command takes one or two parameters:

image "URL" ["CSSClass"]

For example:

image "/images/view.jpg"
image "/images/view.jpg" "scene"

The first form emits <img src="/images/view.jpg">; the second adds class="scene". The URL must be served by your .media manifest or by the webserver's document root. There is no alignment keyword or alt-text argument — use the CSS class and your stylesheet to position the image. See the Multimedia chapter for the corresponding Glk form and a fuller treatment.

Google Sign-In Authentication

The cgijacl and fcgijacl interpreters can optionally bind each request to a verified Google account. When enabled, a player's signed-in identity becomes their user_id, so the auto-save the interpreter restores on every move belongs to that account no matter which browser, device or network they're playing from. The flow is opt-in — if you don't configure it, the existing anonymous flow with randomly-generated user IDs continues to work unchanged.

Enabling sign-in

Sign-in is enabled by setting two directives in cgijacl.conf:

google_client_id   "1234567890-abcdefg.apps.googleusercontent.com"
session_secret     "long-random-string-known-only-to-this-server"
session_max_age    2592000        # optional, in seconds; default 30 days

The google_client_id is the OAuth 2.0 client ID issued by the Google Cloud console for your site. The session_secret is an arbitrary high-entropy string used to HMAC the cookie the interpreter issues after a successful sign-in — anyone who knows this secret can forge sessions, so keep it out of source control and rotate it if you suspect it has leaked. session_max_age sets how long a signed cookie remains valid; the default is 30 days. If either google_client_id or session_secret is empty, sign-in is treated as disabled and the rest of this section is skipped — useful for development copies of a game.

The auth endpoints

The interpreter reserves the request parameter auth for sign-in workflow messages. Two values are recognised:
RequestBehaviour
?auth=google&credential=token The frontend calls this URL after Google Identity Services (GIS) returns an ID token to the page. The interpreter verifies the token against Google's live JWKS, builds a signed session cookie, and returns a small JSON body ({"ok":true,"sub":"sub"}) with a Set-Cookie: jacl_session=... header. The frontend should then reload the page (or restart its Ajax loop) so the new cookie is picked up. On verification failure the interpreter replies 401 Unauthorized with {"ok":false}; the player remains anonymous.
?auth=logout Clears the jacl_session cookie by re-issuing it with Max-Age=0 and returns {"ok":true}. The next request from the browser will fall through to the anonymous flow.

Neither endpoint runs the game's normal command-processing loop; both short-circuit the request after sending their JSON response.

The session cookie

On successful sign-in the interpreter sets a cookie named jacl_session. The cookie value is sub.expiry.hmac where sub is the Google account identifier, expiry is a Unix timestamp, and hmac is a SHA-256 HMAC of sub.expiry using session_secret as the key. The cookie is set with HttpOnly, SameSite=Lax, and (if the request was over HTTPS) Secure. On every subsequent request the interpreter verifies the HMAC in constant time, checks that the expiry is in the future, and if both pass sets user_id to google_sub for the duration of the request. A missing, malformed or expired cookie causes the request to fall through to the existing anonymous flow.

Reading auth state from the game

The interpreter surfaces three game-visible variables every request so your +header, +footer or other UI code can render appropriately:
ContainerTypeContents
google_client_idcstring The configured client ID from cgijacl.conf. Populated on every request — even anonymous ones — so the frontend can render the GIS Sign-In button. Empty if sign-in isn't enabled.
google_subcstring The verified Google account identifier from the cookie, or empty if the player is anonymous on this request.
google_signed_incinteger Set to 1 if a valid session cookie was presented this request, 0 otherwise. Cheaper to test than comparing google_sub to an empty string.

A typical header snippet that renders a sign-in button for anonymous players and a sign-out link for signed-in ones:

{+header
if google_signed_in = 1
   write "<div class=~auth~>Signed in as " google_sub
   write " — <a href=~?auth=logout~>sign out</a></div>"
else
   if google_client_id != ""
      write "<div id=~g_id_onload~ data-client_id=~"
      write google_client_id "~ data-callback=~handleCredential~></div>"
      write "<div class=~g_id_signin~></div>"
   endif
endif
}

The handleCredential Javascript function on your page receives the GIS callback's {credential: "<jwt>"} object and submits it to ?auth=google&credential=jwt via fetch or XMLHttpRequest. On ok:true reload the page; the cookie picked up on the next request will resolve user_id to google_sub.

User ID resolution and auto-save migration

The interpreter's user_id resolution order is:

  1. A valid jacl_session cookie ⇒ google_sub.
  2. A user_id parameter or cookie passed by the request (legacy anonymous flow).
  3. The REMOTE_USER environment variable, if the webserver supplied one.
  4. A fresh anonymous ID (anon_ followed by 32 hex characters drawn from /dev/urandom).

Whichever route resolves first wins. A signed-in cookie always beats an anonymous one for the same browser, so a player who signs in mid-game does not get bounced back to the start.

On a player's first sign-in the interpreter looks for an anonymous auto-save (prefix-anon-id.auto in the temp directory) and, if a matching signed-in auto-save (prefix-google_sub.auto) does not yet exist, renames the anonymous file across. This means signing in does not reset the game in progress. Manual saves (bookmark and named) are intentionally left under the anonymous user_id — only the auto-continue file is migrated.
The signed-in slot is per-Google-account, not per-browser. A player who logs into Google in two browsers will see the same auto-save on both. The signed-out slot is per-browser (cookie-scoped). This is the behaviour most players expect and is the whole reason for sign-in.

The Media File

When played with the cgijacl interpreter, each game will require a corresponding .media file in order to serve multi-media content such as images. This file must have the same name as the game, with a .media suffix in place of the .jacl suffix. For example, the game grail.jacl has a matching media file called grail.media. As this game only has one image, its media file only has one line:

/grail.jpg image/jpeg images/grail.jpg

Each line of a media file has three columns separated by white space. The first column is the URL of the file as specified in the HTML of your game. The second column is the file's MIME type. The third column is the name of the file on the local system to send when this file is requested. Files not beginning with a forward slash are relative to the directory containing the gamefile.

If awk is installed on your system, the media file can be automatically generated from the .jacl game file using the build-media-file script in the bin directory of the JACL distribution. This script accepts the name of the game file as its only parameter and sends its output to standard out. It also assumes that all files live in an images directory beneath the directory containing the game file. build-media-file is used with a command like:

 build-media-file grail.jacl > grail.media

JACL games played using the fcgijacl interpreter and a full webserver do not require a .media file. The path to the image specified in the HTML of your game will be looked for in the document root of your webserver.

Back to Contents