Hangman.jse - a web game
This is a ScriptEase:Webserver Edition version of the popular word guessing game.The rules of hangman are very simple. You have eight chances to incorrectly guess a letter in the word. With each wrong guess, part of a body is drawn on a gallows. After eight wrong guesses, the entire body is on the gallows and the game is lost. The game is won by guessing the word. At any time you can guess the entire word by typing it into the textbox that appears above the letters.
This script uses a form with multiple submit buttons, one for each letter of the alphabet. There is also a text filed. The user guesses a letter by clicking on the appropriate button or by entering the guess in the text field and hitting return. The text field may also be used to guess the entire word at once.
To identify which button was pressed, the script calls cgi.getVar() with no parameters, which returns an array of all the values passed. These values are parsed to find one in the proper form, i.e. one that is one letter long.
A game of hangman in progress
The first part of the script is a list of #include statements and #define statements. They let the computer know where to find the libraries and .gifs used in the game, and define the URLs for links.
Then comes a list of the words that the computer uses for you to guess from. You can add to this list, because the number of words in the list, stored in the variable WordCount, is regenerated every time the script is run.
#include "tinyhtml.jsh"
#define IMAGE_URL "/sewse/hangman/%s"
#define HANGMAN_URL "/www.nombas.com/us/sewse.nlm?n:/hangman/hangman.jse"
Words={ "OVEN", "JACKKNIFE", "SYRINGE", "ABSORBENT",
"FRAGMENT", "TOOTHBRUSH", "BRISTLING", "HOUSEFLY"
};
WordCount = SElib.getArrayLength(Words);
#define ALLOWED_WRONG_GUESSES 7
// default message
Message = "What's your guess?";
The script uses a hidden variable, "State", to keep track the word and the letters that have been guessed. When the hangman game runs, the first thing the script does is try to read the "State" variable. If there is no "State" variable, it knows that it is starting a new game, and selects a word for you to guess. The word is stored as a number to prevent cheating by looking at the source of the document. If the number is 0, for example, the word selected will be the first word on the list.
If state exists, the computer parses it into its component parts: the number corresponding to which word is the one being guessed, and a string consisting of all letters that have been guessed. It does this using Clib.sscanf, a function, which parses strings for data using a format string and returns the number of patterns matched. If one pattern is matched, then there are no letters in SelectedLetters, so Selected Letters is set to an empty string; if two patterns are matched, they will be the WordIndex and the Selected Letters string.
// see if the variable 'state' exists
if ( !(State = cgi.getVar("state")) )
{
// this is a new game, select word and make plain form
Clib.srand(); // randomize number generator
WordIndex = Clib.rand() % WordCount;
SelectedLetters = "";
}
else
{
// Use sscanf to extract WordIndex and Selectedletters
// Word Index is an integer, and SelectedLetters is a string
// of letters representing the guesses so far
if ( 1 == Clib.sscanf(State,"%u %s",WordIndex,SelectedLetters) )
SelectedLetters = "";
else
Clib.assert( 2 == Clib.sscanf(State,"%u %s",WordIndex,
SelectedLetters) );
}
The next line Vars = cgi.getVar() checks to see what has been passed to the script. When cgi.getVar() has no parameters passed to it, it returns an array of all the parameters passed to the script. In this script there are two values passed: the hidden string 'state', and a string consisting of the letter pressed (if a letter button was pressed) or a string consisting of the word guessed (if a word was guessed).
The for loop following the call to cgi.getVar() looks for a string that is only one character long (!Vars[i][1] will return TRUE if there is no character in the second position of the array) and whose only character is a letter. If it finds such a variable, it sets NewLetter to be equal to the letter found.
If the computer gets through the loop without finding such a variable, it assumes that instead of guessing a single letter, the player guessed the whole word, so it tries to extract the word with another call to cgi.getVar(), looking for the variable "guess". The word guessed is made all upper case with the Clib.strupr() function and stored in the variable NewLetterStr.
Vars = cgi.getVar();
for ( i = 0; Vars[i]; i++ ) {
if ( !Vars[i][1] && Clib.isascii(Vars[i][0]) ) {
NewLetter = NewLetterStr[0] = Vars[i][0];
break;
}
}
if ( !Vars[i] ) {
// didn't press button, so get entire guess
Clib.assert( NewLetterStr = cgi.getVar("guess") );
Clib.strupr(NewLetterStr);
NewLetter = NewLetterStr[0];
}
The script then checks to see if NewLetterStr is one letter long or not. This is in case the player entered their guess on the keyboard instead of pressing one of the letter buttons. If not, we check to see if the word they entered matches the word being guessed with a Clib.strcmp() function. If it is, all of the letters in the word are copied into the string of selected letters. If not, the message is set to "Wrong!" and a letter is selected to be used as a guess - in other words, if you guess the word wrong, you are told one letter which isn't in the word.
If NewLetterStr is only one letter long (this is the else part of the if statement described in the previous paragraph) the script checks to make sure it's a letter, and then checks to see whether it's been guessed or not. If it passes these two tests, it will be added to the list of guessed letters. The length of the target word is then calculated and stored in the variable WordLen.
// See if the string returned is only one byte long
if ( 1 != Clib.strlen(NewLetterStr) )
{
// See if they guessed the word correctly, if correct
// they win. else pick a letter to be a wrong choice
if ( !Clib.strcmp(NewLetterStr,Word = Words[WordIndex]) )
Clib.strcpy(SelectedLetters,Word);
else
{
// they guessed the word incorrectly
Message = "Wrong!";
// Step through the guess until an incorrect letter
// is found to use as a guess
for ( i = 0; i < Clib.strlen(NewLetterStr); i++ )
{
if ( !Clib.strchr(Word,NewLetterStr[i]) )
{
if ( !Clib.strchr(SelectedLetters,NewLetterStr[i]) )
{
NewLetterStr[i+1] = 0;
Clib.strcat(SelectedLetters,NewLetterStr+i);
}
break;
}
}
}
}
else if ( !Clib.isalpha(NewLetter) )
Message = "Choose a letter between A and Z, inclusive.";
else if ( Clib.strchr(SelectedLetters,NewLetter) )
Clib.sprintf(Message,"You already selected %c.",NewLetter);
else
Clib.strcat(SelectedLetters,NewLetterStr);
WordLen = Clib.strlen(Word = Words[WordIndex]);
There are three more sections before we have enough information to begin sending the HTML document back to the server. The first two of these sections are each headed with a comment to tell you what they do: build a partially completed display of the word being guessed and build a list of wrong letters guessed thus far. The word display and list of wrong letters are stored in strings, because they need to be output back to the client as part of the game display. The final section decides whether you have won or lost, or are to keep playing.
The 'build display of word' section starts by setting Solved = True. If you can make it through this section without changing Solved to false, you win! It then starts creating a variable DisplayWord to hold the word. This starts off with "WORD:" and then tests each letter in turn, seeing if the letter appears in the string of SelectedLetters, and adding it to DisplayWord if it is. If it isn't, it adds a blank instead, and sets Solved=False.
'Build a list of wrong letters' does much the same thing in reverse. It goes through the list of SelectedLetters and checks to see if they are a part of the Word. If not, they are added to the string WrongChoices. Each time a letter is added to this string, WrongCount is incremented by one.
WrongCount is the variable used to see whether you've lost the game or not. When it is equal to ALLOWED_WRONG_GUESSES (defined at the beginning of the script), the computer checks whether you've guessed all the letters or not, and sets Message accordingly, from it's default string "What's your guess?" to either "You lose. The word was ..." or "You win."
The actual construction of the HTML document now begins with the cgi.out statements. There are only two cgi.out tags in this script; the first outputs the content line (to tell the browser that the document is an HTML document), and the second contains the entire contents of the HTML document. This script uses the tag() function in tinyhtml.jsh, a flexible function that makes an HTML tag from whatever its first parameter is. The second parameter of tag consists of the attributes that will be passed to the tag, or NULL if the tag takes no attributes. All following parameters will be enclosed within the beginning and end tags.
Most of the these parameters are functions in their own right, which return strings. Remember that when a function appears inside a statement, the function can effectively be substituted with what it returns. So if you had a function that returned the complete works of Shakespeare as a string, the line:
tag("B", NULL, Shakespeare())
would insert the complete works of Shakespeare into your document, in boldface using the "<B>" tag which requires no attributes.
HangmanImage() is a function that inserts the hangman image into the document. It determines what state the game is in (won, lost or how many wrong guesses have been made) and inserts the appropriate image name into the variable img. The return statement of this function,
return tag("IMG",
Clib.rsprintf( `SRC= ` IMAGE_URL ` " ALIGN=LEFT
WIDTH=180 HEIGHT=216`, img));
creates the following string to be inserted into the document:
<IMG SRC="hangwin.gif" ALIGN=LEFT WIDTH =180 HEIGHT=216>
Then the variable DisplayWord, which was created earlier in the script, is inserted into the document within a pair of <H3>/</H3> tags. This is done with the tag function again.
The next two lines are pretty cryptic. They use the conditional operator ?. The first of these two statements says "if WrongCount = 0 (i.e., no wrong letters have been guessed), put an empty string in the document; otherwise put in the string of WrongChoices formatted earlier. The WrongChoices string is formatted inside HTML <P>/</P> tags.
The second says "if the game is won OR if the game is lost, add the message to the string; otherwise add the string returned by the function HangmanForm()." The message will be either you won or you lost. Hangman form returns both the message string (What's your guess?, the default) and a series of INPUT fields: a text field so you can guess the entire word at once, plus 26 buttons for each of the letters of the alphabet, plus the hidden field, state, which contains the index number of the word being guessed at and a string of the letters that have already been guessed. Note that the function HangmanForm() has only two statements, and sprintf statement and a return. The return statement is so complex that it is several lines long, and contains functions itself (e.g. the Letters() function, which builds up the buttons for the letters of the alphabet).
When the script ends, the HTML text is sent back to the client. They can then make more letter selections or select a new word if they correctly guessed the word.
Troubleshooting: frequently encountered problems
The browser says "Document returns no data" (Netscape)
Usually when this happens there is no content line put out to the server. A content line looks like this:
cgi.out("Content-Type: text/html\r\n\r\n");
and basically tells the browser what kind of document to expect. In most cases, it will be an html document, but it could be a .gif or .jpg image. The content type line should be the first line sent back to the client.
Some browsers are able to parse out the type of file from its contents, and therefore do not need this line. It's always a good idea to include it though, since you have no idea which browser your clients may be using.
You can also get this message if you are using the functions in html.jsh or tinyhtml.jsh, and forget to use them inside a call to cgi.out. This means that basically you format the page but never send it back to this client.
I get an error message stating that the error is in a Nombas-supplied .jsh file
Usually when this happens the function reporting the error was passed a parameter in the wrong format (e.g., a string instead of an integer, or a misspelled variable name). When the function tries to use the data, it can't, so it reports an error. Make sure all of the variable names are correct, and of the proper type for the function returning the error. The IDE Debugger will help you isolate the error.