Chapter 9
Games
CONTENTS
Web-based games are a realm in which JavaScript shines. Although you
don't need JavaScript to write Web-based games in which the user interacts
with the computer, as a rule games written without JavaScript are much
slower and thus less fun.
The scenario
Most people know the game of hangman from grade school. The first player
chooses a word and states how many letters it contains; the second player
tries to guess the word. The second player picks a letter, and if that
letter is present in the word, the first player identifies where the letter
appears in the word. If the letter isn't present in the word, the first
player draws a stick figure of a person being hanged, one body part at
a time. As I recall, you draw the head, followed by the body, the two arms,
and the two legs. If the second player makes six wrong guesses, the stick
figure is complete, and the first player has stumped the second player.
The
requirements
You will create a Web page that allows the user to play hangman against
the computer. The computer, through the Web page, will choose the word,
and the user will attempt to guess the word.
When the word is chosen, a string of underlines separated by spaces
is displayed, one underline per letter. For example, if the word "syzygy"
is chosen, this is displayed:
_ _ _ _ _ _
The hangman's gallows is also displayed:
+---+
|/ |
| 0
|
|
|
+======
When the user guesses a letter, the letter is displayed if it is in
the word. For example, if the word is "syzygy" and the user guesses
"y," the display is updated like this:
_ y _ y _ y
If, on the other hand, the letter is not in the word, another character
of the hanged man is added to the gallows. For example, if the word is
"syzygy" and the user guesses "e," the gallows display
is updated like this:
+---+
|/ |
| @
|
|
|
+======
On incorrect guesses, the gallows display acquires, in turn, the head,
the body, the left arm, the right arm, the left leg, and the right leg.
The final gallows display, if the user is stumped, looks like this:
+---+
|/ |
| @
| -|-
| / \
|
+======
The user should not be penalized for guessing the same letter twice,
and if possible, should not be permitted to guess the same letter twice.
The user is given a score, based on how many letters he or she guessed
wrong. A running average (the total scores divided by the total games played)
should be displayed.
The
solution (without JavaScript)
The conventional solution (non-JavaScript) to writing a game page is
to first display a page with the rules and a button to indicate that the
user is ready to start playing the game. When the user presses the button,
a CGI program generates the first HTML page that the user will play on.
The first word is chosen and is contained in an INPUT HIDDEN element. The
CGI program writes the HTML to display the underlines and the gallows,
and the page includes an INPUT element that the user can use to choose
the letter. The user selects a letter, and the form data is sent back to
the CGI program. The CGI program then evaluates the new letter, creates
a new page with the letter showing or another character in the hanged man.
The flow looks like this:
User sees welcome page
User presses button to start
--------------------------------------> CGI program activated
New page is displayed <----------------
User selects letter ------------------> CGI program evaluates letter
New page is displayed <----------------
And on and on it goes, until the user guesses the word or is stumped.
The heart of this solution is the CGI program that creates each new
page. The HTML that you write is almost an afterthought. It simply presents
the rules and provides a FORM element with a SUBMIT button, like the code
in Listing 9.1, which produces the form shown in Figure 9.1.
Figure
9.1 : Welcome page.
Listing 9.1: Welcome page
<HTML>
<HEAD>
<TITLE>Hangman</TITLE>
</HEAD>
<BODY>
<P>
Welcome to Hangman! The object of this game is to guess a
word without getting hanged! You can get five letters wrong
- and then you're hanged!
</P>
<FORM ACTION="http://someserver.somewhere/cgi-bin/hangman"
METHOD="get">
<INPUT TYPE="submit" VALUE="Play Hangman">
</FORM>
</BODY>
</HTML>
The
solution (with JavaScript)
The big problem with the non-JavaScript solution is the enormous expense
in bandwidth. With every guess, a new HTML page is downloaded from the
server. That's a lot of traffic!
From the user's perspective, this translates to a slow game. It's okay
for the user to deliberate about his or her next move, but the computer
is expected to respond instantly! Instead, the user gets to watch the status
bar display the number of seconds until the next page is downloaded.
A game of any length-where lots of screens are being loaded-will also
eat your user's history. If the user had visited a page before playing
the game and wanted to go back to it, the user would be out of luck unless
he or she had bookmarked that page.
With JavaScript, the user can get the desired instant feedback.
First, let's lay out the screen. You need a frame that holds the current
results-the gallows and the underlines. Another frame holding the running
average would be nice. And you need a frame to get the letters from the
user. A simple frame document like this will suffice:
<HTML>
<HEAD>
<TITLE>Hangman II</TITLE>
</HEAD>
<FRAMESET ROWS="*,*">
<FRAMESET COLS="*,*">
<FRAME SRC="empty.htm" NAME="scoreboard">
<FRAME SRC="empty.htm" NAME="playingfield">
</FRAMESET>
<FRAME SRC="hangctrl.htm" NAME="controls">
</FRAMESET>
</HTML>
The empty.htm file used in the scoreboard and playingfield frames is
a simple placeholder file that is repeatedly overwritten as the game progresses.
The hangctrl.htm file provides the user controls and the JavaScript code
to update the scoreboard and playingfield frames.
Let's start with the score frame. This frame simply displays the running
average. To do that, you need data-the current sum of scores and the number
of games played. Because the user may play hangman, go visit some other
pages, and then come back and play another game of hangman, let's store
the data in a cookie. This is a data structure that your JavaScript pages
can use to store data on the user's disk and retrieve it later. While you
can store much more, you only need to store a variable name and a value.
The function that initializes the scoreboard frame will try to load the
total points and total words from cookies. If they aren't available, such
as when a user plays the games for the first time, the function will create
cookies with values of 0 for the total points and total words. Normally,
the value displayed will be the total points divided by the total words
played, but with initial values of 0, that doesn't work. Division by zero
is an illegal operation, so the function to initialize the scoreboard has
to take that into account. Finally, the function will display the score
on the scoreboard frame:
var totalPoints;
var totalWords;
var runningAverage;
function initializeScoreBoard()
{
var tP = getCookie("HMPoints");
if (tP == null)
{
neverPlayed();
}
else
{
var tW = getCookie("HMCount");
if (tW == null)
{
neverPlayed();
}
else
{
totalPoints = eval(tP);
totalWords = eval(tW);
}
}
if (totalWords == Ø)
{
runningAverage = Ø;
}
else
{
runningAverage = Math.round(1ØØ * (totalPoints / totalWords));
}
displayScoreBoard();
}
The function getCookie() tries to find a cookie with the specified name;
it returns the special value null if no such cookie exists. Otherwise,
it returns the specified cookie's value as a string:
function getCookie(s)
{
var target = s + "=";
var targetLength = target.length;
var index = Ø;
while (1) // search while 1 is nonzero, i.e., forever
{
var offset = index + targetLength;
if (document.cookie.substring(index, offset) == target)
{
var tail = document.cookie.indexOf(";", offset);
if (tail == -1)
{
tail = document.cookie.length;
}
return unescape(document.cookie.substring(offset, tail));
}
index = 1 + document.cookie.indexOf(" ", index);
if (index == Ø || index >= document.cookie.length)
{
return null;
}
}
return null;
}
The getCookie() function searches the document's cookie property for
the specified name followed by an equal sign. If that string is found,
the value is the substring that follows the equal sign and ends with a
semicolon. If there is no semicolon, the rest of the cookie property string
is returned. In either case, the value is passed to the unescape() function
to translate escaped characters back into normal text. If the name= string
is not found, getCookie() scans ahead to the next space and searches again.
If the end of the document's cookie property is reached, the cookie is
not there and a null is returned.
The neverPlayed() function creates new total points and total words
cookies and sets them to zero; it also sets the variables totalPoints and
totalWords to zero:
function neverPlayed()
{
totalPoints = Ø;
totalWords = Ø;
createCookie("HMPoints", Ø);
createCookie("HMCount", Ø);
}
The createCookie() function is very simple; it runs the value parameter
through the escape() function to translate special characters like spaces
and semicolons into their hexadecimal values. It then creates the cookie:
function createCookie(name, value)
{
document.cookie = name + "=" + escape(value);
}
Finally, initializeScoreBoard() calls displayScoreBoard() to fill in
the scoreboard frame. This creates a new HTML page that uses buttons to
contain the score. Notice that the initializeScoreBoard() function multiplied
the runningAverage value by 100 and rounded it off-that was to make displayScoreBoard()
easier to write and understand:
function displayScoreBoard()
{
parent.scoreboard.document.close();
parent.scoreboard.document.open();
parent.scoreboard.document.open();
openScoreBoardTag("HTML");
openScoreBoardTag("BODY");
openScoreBoardTag("CENTER");
openScoreBoardTag("FORM");
openScoreBoardTag("CODE");
writeScoreBoard("Average misses: ");
openScoreBoardTag("INPUT TYPE=\"button\" VALUE=\""
+ Math.floor(runningAverage / 1ØØ) + "\"");
openScoreBoardTag("INPUT TYPE=\"button\"" +
" VALUE=\".\"");
openScoreBoardTag("INPUT TYPE=\"button\" VALUE=\""
+ (Math.floor(runningAverage / 1Ø) % 10) +
"\"");
openScoreBoardTag("INPUT TYPE=\"button\" VALUE=\"" +
(Math.floor(runningAverage) % 1Ø) + "\"");
closeScoreBoardTag("CODE");
closeScoreBoardTag("FORM");
closeScoreBoardTag("CENTER");
closeScoreBoardTag("BODY");
closeScoreBoardTag("HTML");
}
The displayScoreBoard() function closes the scoreboard frame and then
opens it again; the open() call is made twice to get around some platform-specific
browser problems. The functions openScoreBoardTag(), closeScoreBoardTag(),
and writeScoreBoard() write the strings to the scoreboard frame; these
functions make it easier to keep track of the logical structure of the
document being written:
function writeScoreBoard(text)
{
parent.scoreboard.document.write(text);
}
function openScoreBoardTag(tag)
{
writeScoreBoard("<" + tag + unescape("%3E"));
}
function closeScoreBoardTag(tag)
{
openScoreBoardTag("/" + tag);
}
Having set up the scoreboard, you'll need some words to display on the
playingfield frame. Using the MakeArray() function from Netscape's online
documentation (see Appendix F), you'll create your word list:
function MakeArray(n)
{
this.length = n;
for (var i = 1; i <= n; i++)
{
this[ i ] = Ø;
}
return this;
}
var wordList;
function initializeWordList()
{
wordList = new MakeArray(1Ø);
wordList[ Ø ] = new Word("syzygy");
wordList[ 1 ] = new Word("pendulum");
wordList[ 2 ] = new Word("diphtheria");
wordList[ 3 ] = new Word("phantasm");
wordList[ 4 ] = new Word("escutcheon");
wordList[ 5 ] = new Word("marzipan");
wordList[ 6 ] = new Word("quincunx");
wordList[ 7 ] = new Word("troglodyte");
wordList[ 8 ] = new Word("jitterbug");
wordList[ 9 ] = new Word("vivacious");
}
Here I've created ten words; you'd probably want to use more than that.
The Word object contains a BOOLEAN flag that indicates whether the word
has been played before, the word itself, and the word's length. The length
of the word is accessed so frequently that it's not a bad idea to make
it a property of the Word object. As with the total points and total words,
let's store the played flag in a cookie; that way, the user isn't given
the same word twice:
function Word(word)
{
var playValue = getCookie("HMW" + word);
if (playValue == null)
{
this.played = false;
createCookie("HMW"+word, "Ø");
}
else
{
if (eval(playValue) == Ø)
{
this.played = false;
}
else
{
this.played = true;
}
}
this.word = word;
this.wordLength = word.length;
}
Note that the word is prefaced with "HMW"; this is to avoid
accidental collisions with other cookies. If the cookie's value is "0",
the word has not been played before. If the cookie isn't found, a new cookie
with a value of "0" is created.
Keeping track of which words a user has guessed does place a burden
on you to provide new words every so often. (Otherwise, what happens when
the user has played all of the words?) But this is not necessarily a harsh
task. The same kind of CGI programs that provide page hit counts can be
adapted to insert a random set of words into the page.
Having defined some words, it's time to put up the gallows! The function
newWord() sets up a new word. In so doing, it has to take into account
that it might be called in the middle of play of another word, unless you
want to lock out the user from giving up in the middle. A variable, buttonsActive,
is false if there is no active game in progress and true in the middle
of a game.
The newWord() function then has to select a word. Because the Math object's
random() method does not work on all platforms, I used the least significant
digit of the current time's seconds field to select which word to use.
The word list is scanned for the nth unplayed word, where n
is the digit used. If there are fewer than n unplayed words left,
the set of unplayed words is reused in an "eeny meeny miny mo"
fashion. If there are no unplayed words left, the user is informed of this
fact.
After a word has been selected, its index is noted in a global variable,
currentWord. An array of 26 BOOLEAN values is created and initialized to
all false; this keeps track of which letters have been guessed. The score
is set to zero, and the playing field is drawn. Finally, the buttonsActive
flag is set to true-the game is afoot:
var guessedLetters;
function newWord()
{
if (buttonsActive == true)
{
if (confirm("Are you sure you want to give up?") == true)
{
score = 6;
alert("The word was " + wordList[ currentWord ].word);
gameOver();
}
return;
}
var now = new Date();
var j = now.getSeconds() % 1Ø;
var index = -1;
for (var i = Ø; i <= j; i++)
{
for (k = Ø; k < 1Ø; k++) // loop keeps us from circling forever
{
index++;
if (index >= 1Ø)
{
index = Ø;
}
if (wordList[ index ].played == Ø)
{
break;
}
if (index >= 1Ø)
{
index = Ø;
}
}
if (wordList[ index ].played != Ø)
{
alert("All current words have been played!");
return;
}
}
currentWord = index;
guessedLetters = new MakeArray(26);
for (var k = Ø; k < 26; k++)
{
guessedLetters[ k ] = false;
}
score = Ø;
drawPlayingField();
buttonsActive = true;
}
The drawPlayingField() function is responsible for redrawing the playingfield
frame after each guess, as well as for drawing the initial frame. Like
the displayScoreBoard() function you saw earlier, it creates a new HTML
page and writes it to the playingfield frame. However, it's more dynamic
than displayScoreBoard(): It uses the global variable score to determine
how to draw the gallows and the hanged man, and it uses the guessedLetters
array to determine which letters to fill in, and to display which letters
the user has already guessed. While determining which letters to fill in,
it also notes whether the user has guessed the word:
function drawPlayingField()
{
parent.playingfield.document.close();
parent.playingfield.document.open();
parent.playingfield.document.open();
openPlayingFieldTag("HTML");
openPlayingFieldTag("BODY");
openPlayingFieldTag("PRE");
writePlayingFieldLine("+---+");
writePlayingFieldLine("|/ |");
if (score == Ø)
{
writePlayingFieldLine("| Ø"); // the noose
}
else
{
writePlayingFieldLine("| @"); // the head
}
if (score < 2)
{
writePlayingFieldLine("|");
}
else if (score == 2)
{
writePlayingFieldLine("| |"); // body
}
else if (score == 3)
{
writePlayingFieldLine("| -|"); // body and arm
}
else
{
writePlayingFieldLine("| -|-"); // body and arms
}
if (score < 5)
{
writePlayingFieldLine("|");
}
else if (score == 5)
{
writePlayingFieldLine("| /"); // left leg
}
else
{
writePlayingFieldLine("| / \\"); // both legs
}
writePlayingFieldLine("|");
writePlayingFieldLine("+======");
openPlayingFieldTag("BR");
var missed = Ø;
for (var k = Ø; k < wordList[ currentWord ].wordLength;
k++)
{
var letter =
wordList[ currentWord ].word.substring(k,
k + 1);
var value = codeOf(letter);
if (guessedLetters[ value ] == true)
{
writePlayingField(letter);
}
else
{
writePlayingField("_");
missed++;
}
writePlayingField(" ");
}
openPlayingFieldTag("BR");
if (missed)
{
writePlayingFieldLine("Letters guessed so far:");
for (var x = Ø; x < 26; x++)
{
if (guessedLetters[ x ] == true)
{
writePlayingField(uc.substring(x, x + 1) +
" ");
}
}
}
else
{
writePlayingFieldLine("You guessed it!");
gameOver();
}
closePlayingFieldTag("PRE");
closePlayingFieldTag("BODY");
closePlayingFieldTag("HTML");
}
The functions writePlayingField(), writePlayingFieldLine(), openPlayingFieldTag(),
and closePlayingFieldTag() are very simple and similar to their counterparts
used to draw the scoreboard frame:
function writePlayingFieldLine(text)
{
parent.playingfield.document.writeln(text);
}
function writePlayingField(text)
{
parent.playingfield.document.write(text);
}
function openPlayingFieldTag(tag)
{
writePlayingField("<" + tag + unescape("%3E"));
}
function closePlayingFieldTag(tag)
{
openPlayingFieldTag("/" + tag);
}
The drawPlayingField() function also uses a function, codeOf(), to translate
the letters used in the word into indices to use with the guessedLetters
array:
function codeOf(letter)
{
if (letter == "a" || letter == "A") return Ø;
if (letter == "b" || letter == "B") return 1;
if (letter == "c" || letter == "C") return 2;
if (letter == "d" || letter == "D") return 3;
if (letter == "e" || letter == "E") return 4;
if (letter == "f" || letter == "F") return 5;
if (letter == "g" || letter == "G") return 6;
if (letter == "h" || letter == "H") return 7;
if (letter == "i" || letter == "I") return 8;
if (letter == "j" || letter == "J") return 9;
if (letter == "k" || letter == "K") return 1Ø;
if (letter == "l" || letter == "L") return 11;
if (letter == "m" || letter == "M") return 12;
if (letter == "n" || letter == "N") return 13;
if (letter == "o" || letter == "O") return 14;
if (letter == "p" || letter == "P") return 15;
if (letter == "q" || letter == "Q") return 16;
if (letter == "r" || letter == "R") return 17;
if (letter == "s" || letter == "S") return 18;
if (letter == "t" || letter == "T") return 19;
if (letter == "u" || letter == "U") return 2Ø;
if (letter == "v" || letter == "V") return 21;
if (letter == "w" || letter == "W") return 22;
if (letter == "x" || letter == "X") return 23;
if (letter == "y" || letter == "Y") return 24;
if (letter == "z" || letter == "Z") return 25;
return 26;
}
Now the game is almost ready to play. The user will need some input
elements to pick a letter; you'll use buttons that have a name and value
of each letter in the alphabet, like this:
<INPUT TYPE="button" ONCLICK="guess(this)" NAME="A" VALUE="A">
The guess() function handles the event of a user clicking on the letter.
It checks whether the game is active and does nothing otherwise. The codeOf()
function translates the button's value property to an index, and the guessedLetters
array is checked-if the letter has been guessed, no action is taken. Otherwise,
the letter, both uppercase and lowercase, is scanned for in the currently
selected word. If the letter is not found, the score variable is incremented.
Then the drawPlayingField() function is called to update the playingfield
frame. If the score is 6, the user is informed that the game is over, and
is told the word:
var lc = "abcdefghijklmnopqrstuvwxyz";
var uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
function guess(button)
{
if (buttonsActive == false)
{
return;
}
var index = codeOf(button.value);
if (guessedLetters[ index ] == true)
{
return;
}
guessedLetters[ index ] = true;
var match = false;
for (var j = Ø; j < wordList[ currentWord ].wordLength; j++)
{
if (wordList[ currentWord ].word.substring(j, j + 1) ==
lc.substring(index, index + 1))
{
match = true;
break;
}
if (wordList[ currentWord ].word.substring(j, j + 1) ==
uc.substring(index, index + 1))
{
match = true;
break;
}
}
if (match == false)
{
score++;
}
drawPlayingField();
if (score >= 6)
{
alert("I'm sorry, you lose; the word was " +
wordList[ currentWord ].word);
gameOver();
}
}
The gameOver() function is called when the user has guessed the word,
given up, or couldn't guess the word. It turns the buttons off, adds the
score to the totalPoints variable, and increments the totalWords variable.
The total points and total words cookies are replaced with new values,
and the word cookie is replaced with a value of "1", indicating
that the word has been played. The Word object's played property is also
updated. The scoreboard is then updated via the initializeScoreBoard()
function:
function gameOver()
{
buttonsActive = false;
totalPoints += score;
totalWords++;
createCookie("HMPoints", totalPoints);
createCookie("HMCount", totalWords);
createCookie("HMW" + wordList[ currentWord ].word, "1");
wordList[ currentWord ].played = true;
initializeScoreBoard();
}
There's one last function needed: an initialization function to start
the ball rolling. This is the ONLOAD event handler:
function initialize()
{
buttonsActive = false;
initializeWordList();
initializeScoreBoard();
newWord();
}
The complete code of the JavaScript solution is shown in Listing 9.2.
A sample screen is shown in Figure 9.2.
Figure
9.2 : The JavaScript hangman game in progress.
Listing 9.2: The JavaScript hangman game
<HTML>
<HEAD>
<SCRIPT LANGUAGE="JavaScript">
<!--
var totalPoints;
var totalWords;
var runningAverage;
function initializeScoreBoard()
{
var tP = getCookie("HMPoints");
if (tP == null)
{
neverPlayed();
}
else
{
var tW = getCookie("HMCount");
if (tW == null)
{
neverPlayed();
}
else
{
totalPoints = eval(tP);
totalWords = eval(tW);
}
}
if (totalWords == Ø)
{
runningAverage = Ø;
}
else
{
runningAverage = Math.round(1ØØ * (totalPoints / totalWords));
}
displayScoreBoard();
}
function getCookie(s)
{
var target = s + "=";
var targetLength = target.length;
var index = Ø;
while (1)
{
var offset = index + targetLength;
if (document.cookie.substring(index, offset) == target)
{
var tail = document.cookie.indexOf(";", offset);
if (tail == -1)
{
tail = document.cookie.length;
}
return unescape(document.cookie.substring(offset, tail));
}
index = 1 + document.cookie.indexOf(" ", index);
if (index == Ø || index >= document.cookie.length)
{
return null;
}
}
return null;
}
function neverPlayed()
{
totalPoints = Ø;
totalWords = Ø;
createCookie("HMPoints", Ø);
createCookie("HMCount", Ø);
}
function createCookie(name, value)
{
document.cookie = name + "=" + escape(value);
}
function displayScoreBoard()
{
parent.scoreboard.document.close();
parent.scoreboard.document.open();
parent.scoreboard.document.open();
openScoreBoardTag("HTML");
openScoreBoardTag("BODY");
openScoreBoardTag("CENTER");
openScoreBoardTag("FORM");
openScoreBoardTag("CODE");
writeScoreBoard("Average misses: ");
openScoreBoardTag("INPUT TYPE=\"button\" VALUE=\"" +
Math.floor(runningAverage / 1ØØ) + "\"");
openScoreBoardTag("INPUT TYPE=\"button\"" +
" VALUE=\".\"");
openScoreBoardTag("INPUT TYPE=\"button\" VALUE=\"" +
(Math.floor(runningAverage / 1Ø) % 1Ø) + "\"");
openScoreBoardTag("INPUT TYPE=\"button\" VALUE=\"" +
(Math.floor(runningAverage) % 1Ø) + "\"");
closeScoreBoardTag("CODE");
closeScoreBoardTag("FORM");
closeScoreBoardTag("CENTER");
closeScoreBoardTag("BODY");
closeScoreBoardTag("HTML");
}
function writeScoreBoard(text)
{
parent.scoreboard.document.write(text);
}
function openScoreBoardTag(tag)
{
writeScoreBoard("<" + tag + unescape("%3E"));
}
function closeScoreBoardTag(tag)
{
openScoreBoardTag("/" + tag);
}
function MakeArray(n)
{
this.length = n;
for (var i = 1; i <= n; i++)
{
this[ i ] = Ø;
}
return this;
}
var wordList;
function initializeWordList()
{
buttonsActive = false;
wordList = new MakeArray(1Ø);
wordList[ Ø ] = new Word("syzygy");
wordList[ 1 ] = new Word("pendulum");
wordList[ 2 ] = new Word("diphtheria");
wordList[ 3 ] = new Word("phantasm");
wordList[ 4 ] = new Word("escutcheon");
wordList[ 5 ] = new Word("marzipan");
wordList[ 6 ] = new Word("quincunx");
wordList[ 7 ] = new Word("troglodyte");
wordList[ 8 ] = new Word("jitterbug");
wordList[ 9 ] = new Word("vivacious");
}
function Word(word)
{
var playValue = getCookie("HMW" + word);
if (playValue == null)
{
this.played = false;
createCookie("HMW"+word, "Ø");
}
else
{
if (eval(playValue) == Ø)
{
this.played = false;
}
else
{
this.played = true;
}
}
this.word = word;
this.wordLength = word.length;
}
var buttonsActive = false;
var currentWord;
var guessedLetters;
function newWord()
{
if (buttonsActive == true)
{
if (confirm("Are you sure you want to give up?") == true)
{
score = 6;
alert("The word was " + wordList[ currentWord ].word);
gameOver();
}
return;
}
var now = new Date();
var j = now.getSeconds() % 1Ø;
var index = -1;
for (var i = Ø; i <= j; i++)
{
for (k = Ø; k < 1Ø; k++)
{
index++;
if (index >= 1Ø)
{
index = Ø;
}
if (wordList[ index ].played == Ø)
{
break;
}
if (index >= 1Ø)
{
index = Ø;
}
}
if (wordList[ index ].played != Ø)
{
alert("All current words have been played!");
return;
}
}
currentWord = index;
guessedLetters = new MakeArray(26);
for (var k = Ø; k < 26; k++)
{
guessedLetters[ k ] = false;
}
score = Ø;
drawPlayingField();
buttonsActive = true;
}
function drawPlayingField()
{
parent.playingfield.document.close();
parent.playingfield.document.open();
parent.playingfield.document.open();
openPlayingFieldTag("HTML");
openPlayingFieldTag("BODY");
openPlayingFieldTag("PRE");
writePlayingFieldLine("+---+");
writePlayingFieldLine("|/ |");
if (score == Ø)
{
writePlayingFieldLine("| Ø");
}
else
{
writePlayingFieldLine("| @");
}
if (score < 2)
{
writePlayingFieldLine("|");
}
else if (score == 2)
{
writePlayingFieldLine("| |");
}
else if (score == 3)
{
writePlayingFieldLine("| -|");
}
else
{
writePlayingFieldLine("| -|-");
}
if (score < 5)
{
writePlayingFieldLine("|");
}
else if (score == 5)
{
writePlayingFieldLine("| /");
}
else
{
writePlayingFieldLine("| / \\");
}
writePlayingFieldLine("|");
writePlayingFieldLine("+======");
openPlayingFieldTag("BR");
var missed = Ø;
for (var k = Ø; k < wordList[ currentWord ].wordLength;
k++)
{
var letter =
wordList[ currentWord ].word.substring(k,
k + 1);
var value = codeOf(letter);
if (guessedLetters[ value ] == true)
{
writePlayingField(letter);
}
else
{
writePlayingField("_");
missed++;
}
writePlayingField(" ");
}
openPlayingFieldTag("BR");
if (missed)
{
writePlayingFieldLine("Letters guessed so far:");
for (var x = Ø; x < 26; x++)
{
if (guessedLetters[ x ] == true)
{
writePlayingField(uc.substring(x, x + 1) +
" ");
}
}
}
else
{
writePlayingFieldLine("You guessed it!");
gameOver();
}
closePlayingFieldTag("PRE");
closePlayingFieldTag("BODY");
closePlayingFieldTag("HTML");
}
function writePlayingFieldLine(text)
{
parent.playingfield.document.writeln(text);
}
function writePlayingField(text)
{
parent.playingfield.document.write(text);
}
function openPlayingFieldTag(tag)
{
writePlayingField("<" + tag + unescape("%3E"));
}
function closePlayingFieldTag(tag)
{
openPlayingFieldTag("/" + tag);
}
function codeOf(letter)
{
if (letter == "a" || letter == "A") return Ø;
if (letter == "b" || letter == "B") return 1;
if (letter == "c" || letter == "C") return 2;
if (letter == "d" || letter == "D") return 3;
if (letter == "e" || letter == "E") return 4;
if (letter == "f" || letter == "F") return 5;
if (letter == "g" || letter == "G") return 6;
if (letter == "h" || letter == "H") return 7;
if (letter == "i" || letter == "I") return 8;
if (letter == "j" || letter == "J") return 9;
if (letter == "k" || letter == "K") return 1Ø;
if (letter == "l" || letter == "L") return 11;
if (letter == "m" || letter == "M") return 12;
if (letter == "n" || letter == "N") return 13;
if (letter == "o" || letter == "O") return 14;
if (letter == "p" || letter == "P") return 15;
if (letter == "q" || letter == "Q") return 16;
if (letter == "r" || letter == "R") return 17;
if (letter == "s" || letter == "S") return 18;
if (letter == "t" || letter == "T") return 19;
if (letter == "u" || letter == "U") return 2Ø;
if (letter == "v" || letter == "V") return 21;
if (letter == "w" || letter == "W") return 22;
if (letter == "x" || letter == "X") return 23;
if (letter == "y" || letter == "Y") return 24;
if (letter == "z" || letter == "Z") return 25;
return 26;
}
var lc = "abcdefghijklmnopqrstuvwxyz";
var uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var score;
function guess(button)
{
if (buttonsActive == false)
{
return;
}
var index = codeOf(button.value);
if (guessedLetters[ index ] == true)
{
return;
}
guessedLetters[ index ] = true;
var match = false;
for (var j = Ø; j < wordList[ currentWord ].wordLength; j++)
{
if (wordList[ currentWord ].word.substring(j, j + 1) ==
lc.substring(index, index + 1))
{
match = true;
break;
}
if (wordList[ currentWord ].word.substring(j, j + 1) ==
uc.substring(index, index + 1))
{
match = true;
break;
}
}
if (match == false)
{
score++;
}
drawPlayingField();
if (score >= 6)
{
alert("I'm sorry, you lose; the word was " +
wordList[ currentWord ].word);
gameOver();
}
}
function gameOver()
{
buttonsActive = false;
totalPoints += score;
totalWords++;
createCookie("HMPoints", totalPoints);
createCookie("HMCount", totalWords);
createCookie("HMW" + wordList[ currentWord ].word, "1");
wordList[ currentWord ].played = true;
initializeScoreBoard();
}
function initialize()
{
initializeWordList();
initializeScoreBoard();
newWord();
}
//-->
</SCRIPT>
</HEAD>
<BODY ONLOAD="initialize()">
<CENTER>
<FORM NAME="controller">
<TABLE>
<TR>
<TD>
<INPUT TYPE="button" NAME="start"
VALUE="New Word" ONCLICK="newWord()">
</TD>
<TD>
<CODE>
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="A"
VALUE="A">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="B"
VALUE="B">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="C"
VALUE="C">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="D"
VALUE="D">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="E"
VALUE="E">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="F"
VALUE="F">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="G"
VALUE="G">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="H"
VALUE="H">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="I"
VALUE="I">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="J"
VALUE="J">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="K"
VALUE="K">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="L"
VALUE="L">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="M"
VALUE="M">
<BR>
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="N"
VALUE="N">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="O"
VALUE="O">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="P"
VALUE="P">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="Q"
VALUE="Q">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="R"
VALUE="R">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="S"
VALUE="S">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="T"
VALUE="T">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="U"
VALUE="U">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="V"
VALUE="V">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="W"
VALUE="W">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="X"
VALUE="X">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="Y"
VALUE="Y">
<INPUT TYPE="button"
ONCLICK="guess(this)" NAME="Z"
VALUE="Z">
</CODE>
</TD>
</TR>
</TABLE>
</FORM>
</CENTER>
</BODY>
</HTML>
Improving
the solution
Besides including more words and using a server-include CGI process
to change the words on the fly, you can improve the game itself by adding
a timer to increase pressure on the player. You could do this by adding
a TEXT INPUT element to the form and displaying an incrementing timer in
it. To start the timer you would need a function that should be executed
from newWord(), a function to keep the timer running, and a function to
stop the timer from gameOver(). You would need to modify the scoring to
take the time into account-maybe double the points after some arbitrary
number of seconds.
Modifying
the example for your own use
Besides improving this example, you can devise other interactive games
using this medium. Basically, any game that can be played one-on-one against
the computer should work well in JavaScript-checkers, chess, Othello, Go,
Parcheesi. With GIF or JPEG images, you could even play card games or board
games such as Monopoly. The only limitation is your ability to translate
the rules of the game into JavaScript code!