CONTENTS
Up to this point in the book, you have learned to produce fairly
linear programs. That is, each line of a script gets one chance
to execute (an if ... else
construct can mean certain lines don't execute), and in the case
of functions, each line of the function gets only one chance to
execute each time the function is called.
Most programming relies on the capability to repeat a number of
lines of program code based on a condition or a counter. This
is achieved by using loops.
The for loop enables you
to count through a list and perform the specified command block
for each entry in the list. The while
loop enables you to test for a condition and repeat the command
block until the condition is false.
In this chapter, we take a detailed look at loops and their applications,
including the following:
- Basic concepts of loops
- The for and for
... in loop
- The while loop
- The break and continue
statements
- More about arrays
Loops enable script writers to repeat sections of program code
or command blocks, based on a set of criteria.
For example, a loop can be used to repeat a series of actions
on each number between 1 and 10 or to continue gathering information
from the user until the user indicates she has finished entering
all her information.
The two main types of loops are those that are conditional (while
loops continue until a condition is met or fails to be met) and
those that iterate over a set range (the for
and for ... in loops).
The for loop is the most
basic type of loop and resembles similarly named loops in other
programming languages including Perl, C, and BASIC.
In its most basic form, the for
loop is used to count. For instance, in Listing 6.8 in Chapter 6,
"Creating Interactive Forms," you needed to repeat a
single calculation for each number between 1 and 10. This was
easily achieved using a for
loop:
function calculate(form) {
var number=form.number.value;
for(var num = 1; num <= 10; num++) {
form.elements[num].value = number * num;
}
}
What this loop says is to use the variable num
as a counter. Start with num
at 1, perform the command
block and increment num as
long as num is less than
or equal to 10. In other
words, count from 1 to 10, and for each number perform the command.
In its general form, the for
command looks like this:
for(initial value; condition; update
expression)
The initial value sets up
the counter variable and assigns the initial value. The initial
value expression can declare a new variable using var.
The expression is also optional.
The condition is evaluated
at the start of each pass through the loop, so in this loop:
for(i=8; i<5; i++) {
commands
}
the command block would never be executed because 8
< 5 evaluates to false.
Like the initial value expression, the condition is optional,
and when omitted, evaluates to true
by default.
Note |
If the condition always evaluates to true, it is possible to face an infinite loop, which means the script can never end. In situations where you omit the condition on the for loop, it is important to provide some alternate means to exit
the loop, such as with the break statement, which we will look at later in this chapter.
|
Note |
It is traditional programming to use the variables i, j, k, l, and so on, as counters for loops. This is generally the practice unless a specific variable name adds clarity to the program code. Often, though, programs
use general purpose counters, and these variable names are easily recognizable as counters to experienced programmers.
|
The third part of the for
statement is the update expression.
This expression is executed at the end of the command block before
testing the condition again. This is generally used to update
the counter. This expression is optional, and the counter updating
can be done in the body of the command block, if needed.
To highlight the application of loops, the following script generates
dynamic output to the display window. It asks the user for his
name, followed by his 10 favorite foods for a JavaScript "top
ten" list.
Listing 7.1. Creating a Top Ten list with for
loops.
<HTML>
<HEAD>
<TITLE>for Loop Example</TITLE>
</HEAD>
<BODY>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
var name = prompt("What is your name?","name");
var query = "";
document.write("<H1>" + name + "'s 10 favorite
foods</H1>");
for (var i=1; i<=10; i++) {
document.write(i + ". " + prompt('Enter
food number ' + i,'food') + '<BR>');
}
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</BODY>
</HTML>
This script produces results similar to those in Figure 7.1.
Figure 7.1 : Using loops, you can repeatedly ask users for their 10 favorite foods.
 |
In this example, you use the for loop to count from 1 (i=1) to 10 (i<=10) by increments of one (i++). For each turn through the loop, you prompt the user for a food and write the food out to the window preceded by the
current number using the document.write() method.
|
for loops are used not only
for counting in increments of one. They can be used for counting
in larger quantities. The following line
for(j=2; j<=20; j+=2)
counts from 2 to 20 by twos. Likewise, for
loops can be used to count backward (decrement); the following
line counts down from 10 to 1:
for(k=10; k>=1; k--)
At the same time, simple addition and subtraction are not the
only operations allowed in the update expression. The command
for(i=1; i<=256; i*=2)
will start with i equal to
1 and then proceed to double
it until the value is more than 256. Similarly,
for(j=3; j<=81; j*=j)
repeatedly squares the counter.
Where the for loop is a general-purpose
loop, JavaScript also has the for ...
in loop for more specific applications. The for
... in loop is used to automatically step through
all the properties of an object. In order to understand this,
remember that each property in an object can be referred to by
a number-its index. For instance, this loop
for (j in testObject) {
commands
}
increments j from 0
until the index of the last property in the object testObject.
This is useful where the number of properties is not known or
not consistent, as in a general-purpose function for an event
handler.
For instance, you may want to create a simple slot machine application.
The slot machine can display numbers from 0 to 9-each in a separate
text field in a form. If the form is named slotForm,
then the loop
for (k in slotForm) {
code to display number
}
could be the basis for displaying the results of spinning the
slot machine. With this type of loop, you could easily change
the number of items on the slot machine so that instead of three
text fields, you could have five fields, two fields, or nine fields.
In this example you write a single function to check whether or
not the information the user has entered in a field is a number.
In order to do this, you use the substring()
method learned in Chapter 6, and you assume
that numbers contain only the digits zero through nine plus a
decimal point and a negative sign. The presence of any other character
in a field indicates that the value is not numeric.
This type of function could then be used, for example, in checking
form input. For instance, in Exercise 5.3 (the doubling and squaring
form), you could add the function to the script, as shown in Listing
7.2.
Listing 7.2. Checking input with the isNum()
function.
<HTML>
<HEAD>
<TITLE>for ... in Example</TITLE>
<SCRIPT>
<!-- HIDE FROM OTHER BROWSERS
function checkNum(toCheck) {
var isNum = true;
if ((toCheck == null) || (toCheck == ""))
{
isNum = false;
return isNum;
}
for (j = 0; j < toCheck.length; j++) {
if ((toCheck.substring(j,j+1) != "0")
&&
(toCheck.substring(j,j+1)
!= "1") &&
(toCheck.substring(j,j+1)
!= "2") &&
(toCheck.substring(j,j+1)
!= "3") &&
(toCheck.substring(j,j+1)
!= "4") &&
(toCheck.substring(j,j+1)
!= "5") &&
(toCheck.substring(j,j+1)
!= "6") &&
(toCheck.substring(j,j+1)
!= "7") &&
(toCheck.substring(j,j+1)
!= "8") &&
(toCheck.substring(j,j+1)
!= "9") &&
(toCheck.substring(j,j+1)
!= ".") &&
(toCheck.substring(j,j+1)
!= "-")) {
isNum = false;
}
}
return isNum;
}
function calculate(form,currentField) {
var isNum = true;
var thisFieldNum = true;
for (var field = 0; field < form.length; field
++) {
thisFieldNum = checkNum(field.value);
if (!thisFieldNum)
isNum = false;
}
if (isNum) {
if (currentField == "square")
{
form.entry.value = Math.sqrt(form.square.value);
form.twice.value = form.entry.value
* 2;
} else if (currentField == "twice")
{
form.entry.value = form.twice.value
/ 2;
form.square.value = form.entry.value
* form.entry.value;
} else {
form.twice.value = form.entry.value
* 2;
form.square.value = form.entry.value
* form.entry.value;
}
} else {
alert("Please Enter only Numbers!");
}
}
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</HEAD>
<BODY>
<FORM METHOD=POST>
Value: <INPUT TYPE=text NAME="entry" VALUE=0
onChange="calculate(this.form,this.name);">
Double: <INPUT TYPE=text NAME="twice" VALUE=0
onChange="calculate(this.form,this.name);">
Square: <INPUT TYPE=text NAME="square" VALUE=0
onChange="calculate(this.form,this.name);">
</FORM>
</BODY>
</HTML>
All the number checking takes place in the checkNum()
function:
function checkNum(toCheck) {
var isNum = true;
if ((toCheck == null) || (toCheck == ""))
{
isNum = false;
return isNum;
}
for (j = 0; j < toCheck.length; j++) {
if ((toCheck.substring(j,j+1) != "0")
&&
(toCheck.substring(j,j+1)
!= "1") &&
(toCheck.substring(j,j+1)
!= "2") &&
(toCheck.substring(j,j+1)
!= "3") &&
(toCheck.substring(j,j+1)
!= "4") &&
(toCheck.substring(j,j+1)
!= "5") &&
(toCheck.substring(j,j+1)
!= "6") &&
(toCheck.substring(j,j+1)
!= "7") &&
(toCheck.substring(j,j+1)
!= "8") &&
(toCheck.substring(j,j+1)
!= "9") &&
(toCheck.substring(j,j+1)
!= ".") &&
(toCheck.substring(j,j+1)
!= "-")) {
isNum = false;
}
}
return isNum;
}
 |
You make simple use of the for statement in this example. You start by assuming that the value is a number (var isNum = true;). First you check to make sure the value passed to the function is not the empty string or the null
value, and then you use the loop to move from the first character in the field value to the last, and each time, check whether the given character is a numeric value. If not, you set isNum to false. After the loop, you return the value of
isNum.
|
You check each character to see whether it is a number by using
one if statement with multiple
conditions. Remembering that &&
is the symbol for logical "and," we are saying if the
character doesn't match any number from 0 to 9 or the decimal
point or negative sign, then the entry is not a number.
An alternative approach to comparing the number to each possible
number from 0 to 9 is to use the structure (toCheck.substring(j,j+1)
<= "0" && toCheck.substring(j,j+1) >=
"9" which would check if the digit is a
numeral:
if ((toCheck.substring(j,j+1)
<= "0") &&
(toCheck.substring(j,j+1)
>= "9") &&
(toCheck.substring(j,j+1)
!= ".") &&
(toCheck.substring(j,j+1)
!= "-")) {
isNum = false;
}
In addition to the for loop,
the while loop provides a
different, but similar, function. The basic structure of a while
loop is
while (condition) {
JavaScript commands
}
where the condition is any
valid JavaScript expression that evaluates to a boolean value.
The command block executes as long as the condition is true.
For instance, the following loop counts until the value of num
is 11:
var num = 1;
while (num <= 10) {
document.writeln(num);
num++;
}
A while loop could easily
be used in a testing situation where the user must answer a question
correctly to continue:
var answer = "";
var correct = 100;
var question = "What is 10 * 10?";
while (answer != correct) {
answer = prompt(question,"0");
}
In this example, you simply set answer
to an empty string, so that at the start of the while
loop, the condition would evaluate to true
and the question would be asked at least once.
Now that you have learned both the for
loop and the while loop,
you are ready to build a more complex program.
As an educational tool for children, you are going to build a
calculator to solve the typical problems children get on tests:
If a leaves b at speed c and d leaves e at speed f and they travel
in a straight line toward each other, when will they meet?
The student simply enters the required information into the form
and then either selects the Calculate button to see the correct
answer or a Test button to be tested on the problem. Listing 7.3
contains the code for this program.
Listing 7.3. Travel problem tester.
<HTML>
<HEAD>
<TITLE>Listing 7.3</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
function checkNum(toCheck) {
var isNum = true;
if ((toCheck == null) || (toCheck == ""))
{
isNum = false;
return isNum;
}
for (j = 0; j < toCheck.length; j++) {
if ((toCheck.substring(j,j+1) != "0")
&&
(toCheck.substring(j,j+1)
!= "1") &&
(toCheck.substring(j,j+1)
!= "2") &&
(toCheck.substring(j,j+1)
!= "3") &&
(toCheck.substring(j,j+1)
!= "4") &&
(toCheck.substring(j,j+1)
!= "5") &&
(toCheck.substring(j,j+1)
!= "6") &&
(toCheck.substring(j,j+1)
!= "7") &&
(toCheck.substring(j,j+1)
!= "8") &&
(toCheck.substring(j,j+1)
!= "9") &&
(toCheck.substring(j,j+1)
!= ".") &&
(toCheck.substring(j,j+1)
!= "-")) {
isNum = false;
}
}
return isNum;
}
function checkFieldNum(field) {
if (!checkNum(field.value)) {
alert("Please enter a number in this
field!");
}
}
function checkFormNum(form) {
var isNum = true;
for (field = 0; field <=2; field ++) {
if (!checkNum(form.elements[field].value))
{
isNum = false;
}
}
if (!isNum) {
alert("All Fields Must Be Numbers!");
}
return isNum;
}
function calculate(form) {
if (checkFormNum(form)) {
with (form) {
var time = distance.value
/ (eval(speedA.value) + eval(speedB.value));
result.value = ""
+ time + " hour(s)";
}
}
}
function test(form) {
if (checkFormNum(form)) {
with (form) {
var time = distance.value
/ (eval(speedA.value) + eval(speedB.value));
var answer = "";
while (eval(answer) != time)
{
answer = prompt("What
is the answer to the problem?","0");
}
result.value = ""
+ time + " hour(s)";
}
}
}
// STOP HIDING FROM OTHER BROWSERS -->
</SCRIPT>
</HEAD>
<BODY>
<FORM METHOD=POST>
Distance: <INPUT TYPE=text NAME="distance" onChange="checkFieldNum(this);"><BR>
Speed of Person A: <INPUT TYPE=text NAME="speedA"
onChange="checkFieldNum(this);"><BR>
Speed of Person B: <INPUT TYPE=text NAME="speedB"
onChange="checkFieldNum(this);"><BR>
<INPUT TYPE=button Name="Calculate" VALUE="Calculate"
onClick="calculate(this.form);">
<INPUT TYPE=button Name="Test" VALUE="Test"
onClick="test(this.form);"><BR>
Results: <INPUT TYPE=text NAME=result onFocus="this.blur();">
</FORM>
</BODY>
</HTML>
This script produces results like those in Figures 7.2 and 7.3.
Figure 7.2 : Using the for loop, you can test the form entries before calculating the result.
Figure 7.3 : The while loop enables you to continually test the user for the correct answer.
 |
You make several different uses of loops in this example.
|
In the checkFormNum() function,
you use the for loop to cycle
through all the first three form elements:
for (field = 0; field <=2; field ++)
{
if ((!checkNum(form.elements[field].value)) {
isNum = false;
}
}
You can then simply check whether the field contains a numeric.
You also use a while loop
in the test() function to
perform the testing of the student. In both the test()
and calculate() functions,
you also use a new statement: with.
This command is used where numerous references to an object are
made in a block of code to make the code shorter and easier to
read. For instance, in this script, you refer to the form
object. By using with (form),
you can then write a block of code without the form
prefix on all the properties and method calls.
To illustrate, if you have a function that assigned values to
five fields in a form, you could write it two different ways:
function assign(form) {
form.one.value = 1;
form.two.value = 2;
form.three.value = 3;
form.four.value = 4;
form.five.value = 5;
}
or
function assign(form) {
with (form) {
one.value = 1;
two.value = 2;
three.value = 3;
four.value = 4;
five.value = 5;
}
}
To add even more utility to the for
and while loops, JavaScript
includes the break and continue
statements. These statements can be used to alter the behavior
of the loops beyond a simple repetition of the related command
block.
The break command does what
the name implies-it breaks out of the loop completely, even if
the loop isn't complete. For instance, if you want to give students
three chances to get a test question correct, you could use the
break statement:
var answer = "";
var correct = "100";
var question = "What is 10 * 10?";
for (k = 1; k <= 3; k++) {
answer = prompt(question,"0");
if (answer == correct) {
alert ("Correct!");
break;
}
}
In this loop, the command block gets performed three times only
if the first two answers are incorrect. A correct answer simply
ends the loop prematurely.
The continue statement is
slightly different. It is used to jump to the next repetition
of the command block without completing the current pass through
the command block. For instance, if you want to total three numbers
input with a prompt statement
but want to simply ignore a value if it is not a number, you might
use the following structure (you are assuming the existence of
a similar checkNum() function
to the one you used before):
var total = 0;
var newNumber = 0;
for (i=1; i <=3; i++) {
newNumber = prompt ("Enter a number","0");
if (!checkNum(newNumber))
continue;
total = eval(total) + eval(newNumber);
alert ("You entered " + newNumber + "
and the total is " +
total
+ ".");
}
This loop could be extended to add numbers until the user enters
0 as the new value. You do
this by using a while loop
instead of a for loop:
var total = 0;
var newNumber = "";
while ((newNumber = prompt ("Enter a numer","0"))
!= 0) {
if (!checkNum(newNumber))
continue;
total = eval(total) + eval(newNumber);
alert ("You entered " + newNumber + "
and the total is " +
total
+ ".");
}
The reason you use the prompt in the while
loop's condition is related to when the condition is tested.
In this way, if the user enters 0
at the first prompt, the alert dialog box is never displayed.
In this example, you write a script to play a simple game of tic-tac-toe.
In order to do this, you use nine text entry fields in a single
form to contain the nine spaces of the tic-tac-toe board.
The basic approach is as follows: The user plays first. After
each play by the user, the relevant rows, columns, and diagonals
are checked for a win. If there is no win, you scan each row,
column, and diagonal to see if the computer can win. Then you
check all rows, columns, and diagonals to see if there is a chance
for the user to win. Failing both these scenarios, the computer
simply takes any available space.
In order to implement the game easily, you use a standard naming
system for the fields, such as 11 for the top left corner, 13
for the top right corner and 33 for the bottom right corner. In
this way, you will be able to use loops to quickly scan the board
for combinations.
Tip |
If you find the task a bit overwhelming, skip ahead to the analysis section following the source code to get a better feel for what's being done.
|
Listing 7.4. Tic-tac-toe with for
loops.
<HTML>
<HEAD>
<TITLE>Listing 7.4</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!-- HIDE FROM OTHER BROWSERS
var row = 0;
var col = 0;
var playerSymbol = "X";
var computerSymbol = "O";
board = new createArray(3,3);
function createArray(row,col) {
var index = 0;
this.length = (row * 10) + col;
for (var x = 1; x <= row; x ++) {
for (var y = 1; y <= col; y++) {
index = (x*10) + y;
this[index] = "";
}
}
}
function buildBoard(form) {
var index = 0;
for (var field = 0; field <= 8; field ++) {
index = eval(form.elements[field].name);
form.elements[field].value = board[index];
}
}
function clearBoard(form) {
var index = 0;
for (var field = 0; field <= 8; field ++) {
form.elements[field].value = "";
index = eval(form.elements[field].name);
board[index] = "";
}
}
function win(index) {
var win = false;
// chECK ROWS
if ((board[index] == board[(index < 30) ? index
+ 10 : index - 20]) &&
(board[index] == board[(index
> 11) ? index - 10 : index + 20])) {
win = true;
}
// chECK COLUMNS
if ((board[index] == board[(index%10 < 3) ? index
+ 1 : index - 2]) &&
(board[index] == board[(index%10
> 1) ? index - 1 : index + 2])) {
win = true;
}
// chECK DIAGONALS
if (Math.round(index/10) == index%10) {
if ((board[index] == board[(index <
30) ? index + 11 : index - 22]) &&
(board[index]
== board[(index > 11) ? index - 11 : index + 22]))
{
win = true;
}
if (index == 22) {
if ((board[index] == board[13])
&& (board[index] == board[31])) {
win = true;
}
}
}
if ((index == 31) || (index == 13)) {
if ((board[index] == board[(index <
30) ? index + 9 : index - 18]) &&
(board[index]
== board[(index > 11) ? index - 9 : index + 18]))
{
win = true;
}
}
// RETURN THE RESULTS
return win;
}
function play(form,field) {
var index = eval(field.name);
var playIndex = 0;
var winIndex = 0;
var done = false;
field.value = playerSymbol;
board[index] = playerSymbol;
//chECK FOR PLAYER WIN
if (win(index)) {
// PLAYER WON
alert("Good Play! You Win!");
clearBoard(form);
} else {
// PLAYER LOST, chECK FOR WINNING POSITION
for (row = 1; row <= 3; row++) {
for (col = 1; col <= 3;
col++) {
index = (row*10)
+ col;
if (board[index]
== "") {
board[index]
= computerSymbol;
if(win(index))
{
playIndex
= index;
done
= true;
board[index]
= "";
break;
}
board[index]
= "";
}
}
if (done)
break;
}
// chECK IF COMPUTER CAN WIN
if (done) {
board[playIndex] = computerSymbol;
buildBoard(form);
alert("Computer Just
Won!");
clearBoard(form);
} else {
// CAN'T WIN, chECK IF NEED
TO STOP A WIN
for (row = 1; row <=3;
row++) {
for (col = 1;
col <= 3; col++) {
index
= (row*10) + col;
if
(board[index] == "") {
board[index]
= playerSymbol;
if
(win(index)) {
playIndex
= index;
done
= true;
board[index]
= "";
break;
}
board[index]
= "";
}
}
if (done)
break;
}
// chECK IF DONE
if (done) {
board[playIndex]
= computerSymbol;
buildBoard(form);
} else {
// NOT DONE, chECK
FOR FIRST EMPTY SPACE
for (row = 1;
row <= 3; row ++) {
for
(col = 1; col <= 3; col ++) {
index
= (row*10) + col;
if
(board[index] == "") {
playIndex
= index;
done
= true;
break;
}
}
if
(done)
break;
}
board[playIndex]
= computerSymbol;
buildBoard(form);
}
}
}
}
// STOP HIDING HERE -->
</SCRIPT>
</HEAD>
<BODY>
<FORM METHOD = POST>
<TABLE>
<TR>
<TD>
<INPUT TYPE=text SIZE=3 NAME="11"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
<TD>
<INPUT TYPE=text SIZE=3 NAME="12"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
<TD>
<INPUT TYPE=text SIZE=3 NAME="13"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
</TR>
<TR>
<TD>
<INPUT TYPE=text SIZE=3 NAME="21"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
<TD>
<INPUT TYPE=text SIZE=3 NAME="22"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
<TD>
<INPUT TYPE=text SIZE=3 NAME="23"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
</TR>
<TR>
<TD>
<INPUT TYPE=text SIZE=3 NAME="31"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
<TD>
<INPUT TYPE=text SIZE=3 NAME="32"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
<TD>
<INPUT TYPE=text SIZE=3 NAME="33"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
</TD>
</TR>
</TABLE>
<INPUT TYPE=button VALUE="I'm Done-Your Go">
<INPUT TYPE=button VALUE="Start Over" onClick="clearBoard(this.form);">
</FORM>
</BODY>
</HTML>
This script produces results similar to those in Figure 7.4.
Figure 7.4 : This tic-tac-toe game makes extensive use of loops.
 |
This is the most complex example you have worked on. You combine what you have learned about objects as arrays, loops, and expressions to produce a functional tic-tac-toe game.
|
To understand better exactly what the script does, let's take
a look at each section in turn.
var row = 0;
var col = 0;
var playerSymbol = "X";
var computerSymbol = "O";
board = new createArray(3,3);
Here you declare the global variables and the array object that
you use throughout the script. The board
object is an instance of the createArray
object, which you define using a function later in the script.
You use the board array to
hold an image of the values displayed in the form because it is
easier to work with indexes of an array than the sequential order
of elements in a form.
function createArray(row,col) {
var index = 0;
this.length = (row*10) + col
for (var x = 1; x <= row; x ++) {
for (var y = 1; y <= col; y++) {
index = (x*10) + y;
this[index] = "";
}
}
}
The createArray() function
defines the array object you use in this script. Notice the use
of for loops to define the
object. This type of array definition will be discussed in further
detail later in this chapter. It is important to notice how you
are building the two-digit numeric indexes by multiplying the
row number by 10 and adding it to the column number to produce
indexes such as 11, 12, 13, 21, 22, 23, 31, 32, and 33.
function buildBoard(form) {
var index = 0;
for (var field = 0; field <= 8; field ++) {
index = eval(form.elements[field].name);
form.elements[field].value = board[index];
}
}
buildBoard() displays the
values in the board object
in the form. By cycling through all the elements in the form using
a for loop, you can get the
relevant index from the field.name
using the eval() function,
which converts the name (a string) into a numeric value.
function win(index) {
var win = false;
// chECK ROWS
if ((board[index] == board[(index < 30) ? index
+ 10 : index - 20]) &&
(board[index] == board[(index
> 11) ? index - 10 : index + 20])) {
win = true;
}
// chECK COLUMNS
if ((board[index] == board[(index%10 < 3) ? index
+ 1 : index - 2]) &&
(board[index] == board[(index%10
> 1) ? index - 1 : index + 2])) {
win = true;
}
// chECK DIAGONALS
if (Math.round(index/10) == index%10) {
if ((board[index] == board[(index <
30) ? index + 11 : index - 22]) &&
(board[index]
== board[(index > 11) ? index - 11 : index + 22]))
{
win = true;
}
if (index == 22) {
if ((board[index] == board[13])
&& (board[index] == board[31])) {
win = true;
}
}
}
if ((index == 31) || (index == 13)) {
if ((board[index] == board[(index <
30) ? index + 9 : index - 18]) &&
(board[index]
== board[(index > 11) ? index - 9 : index + 18]))
{
win = true;
}
}
// RETURN THE RESULTS
return win;
}
The win() function requires
more explanation. The function is designed to check all rows,
columns, and diagonals crossing the space indicated by index
to see if there is a win.
For instance, to check the row that index
is in, you need to compare the value of board[index]
with the value to its immediate right and to its immediate left.
At first, it would seem that you could use a statement such as
if ((board[index] == board[index+10])
&& (board[index] == board[index-10]))
to do this. The problem with this is that if the index passed
to the function is the third space in a row, you will be attempting
to look at a fourth, non-existent space in the first condition.
Similarly, if index represents the first space in a row, the second
condition will try looking at board[index-10],
which doesn't exist.
You remedy this situation through the use of conditional expressions.
For instance board[(index < 30) ?
index + 10 : index - 20] evaluates to board[31]
if index is 21
but evaluates to board[12]
if index is 32.
The testing of diagonals also requires some explanation. You start
by checking whether index
represents any space on the diagonal from the top left to the
bottom right. If it does, you check that diagonal, and then if
the space is the middle space on the board, you also check the
diagonal from the top right to bottom left. You finish by checking
whether the top right or bottom left corner is represented by
index; if it is, you check
the second diagonal.
The play() function, which
comes next, is somewhat more complex and also requires more detailed
explanation.
function play(form,field) {
var index = eval(field.name);
var playIndex = 0;
var winIndex = 0;
var done = false;
field.value = playerSymbol;
board[index] = playerSymbol;
You start by declaring global variables and assigning the correct
symbol to the appropriate field form and property of the board
object. You do this so that the user can type any character in
the field she wants to mark for her play.
After this, you use the win()
function to check if the play makes the user a winner.
//chECK FOR PLAYER WIN
if (win(index)) {
// PLAYER WON
alert("Good Play! You Win!");
clearBoard(form);
} else {
If the user has not won, you need to start checking for the best
move by the computer. The first thing to do is to look for a position
that lets the computer win. You do this with a pair of embedded
for loops. These loops enable
you to cycle through each position on the playing board. For each
position, if the value is an empty string (meaning no play has
been made there), you temporarily play the computer's symbol there
and check if that produces a win. If it does, you set the appropriate
variables and break out of the inside for
loop.
Because the break statement
breaks out of the innermost loop only, you end the outer loop
with an if statement to break
out of the outer loop if you have found the winning play.
At the end of the inner loop, you assign the empty string back
to the current position because it did not produce a win, and
you are not going to play there at this point.
// PLAYER LOST,
chECK FOR WINNING POSITION
for (row = 1; row <= 3; row++) {
for (col = 1; col <= 3;
col++) {
index = (row*10)
+ col;
if (board[index]
== "") {
board[index]
= computerSymbol;
if(win(index))
{
playIndex
= index;
done
= true;
board[index]
= "";
break;
}
board[index]
= "";
}
}
if (done)
break;
}
If you have found a winning position, you simply display the play
with buildBoard() and then
inform the user that the computer won.
// chECK IF COMPUTER
CAN WIN
if (done) {
board[playIndex] = computerSymbol;
buildBoard(form);
alert("Computer Just
Won!");
clearBoard(form);
} else {
Next, having failed to find a winning position, it is necessary
to look for potential wins by the user in the form of complete
rows, columns, or diagonals missing only one play by the user.
This is achieved in exactly the same way you looked for a winning
computer play, except this time, you check for plays that would
generate a winning play by the user.
//
CAN'T WIN, chECK IF NEED TO STOP A WIN
for (row = 1; row <=3;
row++) {
for (col = 1;
col <= 3; col++) {
index
= (row*10) + col;
if
(board[index] == "") {
board[index]
= playerSymbol;
if
(win(index)) {
board[index]
= computerSymbol;
playIndex
= index;
done
= true;
board[index]
= "";
break;
}
board[index]
= "";
}
}
if (done)
break;
}
// chECK IF DONE
if (done) {
board[playIndex]
= computerSymbol;
buildBoard(form);
} else {
Having failed to find a winning play for the computer or identified
a potential win on the part of the user, you simply proceed to
find the first empty position and play there. You do this with
another set of embedded for
loops and break out of the loops once you have found the first
empty space.
//
NOT DONE, chECK FOR FIRST EMPTY SPACE
for (row = 1;
row <= 3; row ++) {
for
(col = 1; col <= 3; col ++) {
index
= (row*10) + col;
if
(board[index] == "") {
playIndex
= index;
done
= true;
break;
}
}
if
(done)
break;
}
board[playIndex]
= computerSymbol;
buildBoard(form};
}
}
}
}
Now that you've studied the functions that drive the game, let's
take a look at how you use event handlers in the form. The form
consists of nine identical fields (except for their names), named
according to the scheme of 11, 12, 13, 21, 22, 23, and so on.
Each INPUT tag contains the
same two event handlers:
<INPUT TYPE=text SIZE=3 NAME="31"
onFocus="if (this.value
!= '') {blur();}"
onChange="play(this.form,this);">
In the onFocus event handler,
you are simply checking whether the field the user has selected
is empty. If not, you remove the focus immediately so that the
user is free to play in empty fields only and cannot alter the
content of used spaces.
The onChange event handler
simply calls the play() function,
which records the user's play, checks if the user has won, and
if necessary, chooses a play for the computer.
Now that you have a firm grasp on the concept of loops, you can
look at how for loops can
create the equivalent of one-dimensional arrays in JavaScript.
As you saw in Chapter 4, "Functions
and Objects-The Building Blocks of Programs," JavaScript
has provisions for associative arrays in that object properties
can be referred to as a numeric index of the object. However,
programmers who have studied C, Perl, or Pascal are aware of the
value of arrays of the same type.
That is, you need to be able to define an array as an ordered
set of elements of the same type where the number of elements
can vary each time the array is defined. You can do this using
objects by defining the object function using a for
loop.
Note |
JavaScript provides a pre-built constructor object called Array which does just this. However, in order to understand how this is done, you will build your own here. Some early versions of JavaScript did not provide the Array() object.
|
For instance, to define a numeric array of an unknown number of
elements, you might write the object definition function createArray()
like this:
function createArrary(num) {
this.length = num;
for (var j = 0; j < num; j++) {
this[j] = 0;
}
}
This function creates an array starting with index 0
and assigns all values of the new array to 0.
Using this object, you could then use newArray
= new createArray(4) to create an array of four elements
called newArray. You would
refer to the elements in the array as newArray[0],
newArray[1], and so on.
In this chapter you have learned how to use loops to achieve sophisticated
control over the flow of a function or script. Using for
loops, you can repeat a command block several times, based on
a range and an expression to move through the range. The for
... in loop enables you to cycle through all the properties
in an object. The while loop
works differently in that the associated command block is executed
if a condition is true; otherwise the loop finishes. The break
and continue statements enable
you to alter the flow of a loop by either breaking out of the
loop completely, or prematurely moving on to the next cycle through
the loop. You also learned that loops can be used to create array
objects.
In Chapter 8, "Frames, Documents,
and Windows," you will take a close look at the document
window, the methods it offers, and how to manipulate it. You will
also learn to use frames and take a detailed look at the frames
object.
Command/Extension | Type
| Description |
for |
Statement | Loops based on an initial value, a condition, and an expression
|
for ... in
| Statement | Loops through all the properties in an object, returning the index of the property
|
while |
Statement | Loops based on a condition; continues until the condition is false
|
with |
Statement | Enables a command block to omit an object prefix
|
break |
Statement | Breaks out of the current loop
|
continue
| Statement | Jumps to the next iteration of the current loop
|
Q | an I use the same variable as the counter for more than one loop?
|
A | Yes. As long as the loops are not embedded, you can reuse counter variables. If loops are embedded, using the same name for both loops results in scripts that don't work as expected.
|
Q | I've seen a repeat ... until loop in some other programming languages. Does JavaScript have one?
|
A | No. The repeat ... until loop is similar to the while loop except that it tests its condition at the end of the loop. JavaScript doesn't have this type of loop.
|
- Write while loops to emulate each of these for loops:
a.
for (j = 4; j > 0; j --) {
document.writeln(j + "<BR>");
}
b.
for (k = 1; k <= 99; k = k*2) {
k = k/1.5;
}
c.
for (num = 0; num <= 10; num ++) {
if (num == 8)
break;
}
- In Chapter 4 you learned about recursion
and how to use it for a variety of purposes, including calculating
factorials and exponents. With loops, it is possible to make the
same calculations. Write a function that doesn't use recursion
to calculate factorials.
- In Listing 7.4, the play()
function works but is not too intelligent. Specifically, if the
computer has no obvious winning play and does not immediately
need to prevent the user from winning, no strategy is applied
to the computer's selection.
Rewrite the play() function
so that in this situation, the computer first tries to find a
space so that playing there creates a row or column with two of
the computer's symbols and an empty space.
- while loops can be used
in all three cases:
a.
j = 5;
while (--j > 0) {
document.writeln(j + "<BR>");
}
b.
k = 1;
while (k <= 99) {
k = k * 2 / 1.5;
}
c.
num = 0;
while (num <= 10) {
if (num++ == 8)
break;
}
- A factorial function can easily be written using a single
for loop:
function factorial(num) {
var factorial = 1;
for (var i=2; i<=num; i++) {
factorial *= i;
}
return factorial;
}
- In order to improve the
computer's strategy used at the end of the play()
function, you need to build some complex if
statements into a pair of embedded for
loops. The following is the complete replacement for the play()
function in Listing 7.4:
function play(form,field) {
var index = eval(field.name);
var playIndex = 0;
var winIndex = 0;
var done = false;
field.value = playerSymbol;
board[index] = playerSymbol;
//chECK FOR PLAYER WIN
if (win(index)) {
// PLAYER WON
alert("Good Play! You Win!");
clear(form);
} else {
// PLAYER LOST, chECK FOR WINNING POSITION
for (row = 1; row <= 3; row++) {
for (col = 1; col <= 3;
col++) {
index = (row*10)
+ col;
if (board[index]
== "") {
board[index]
= computerSymbol;
if(win(index))
{
playIndex
= index;
done
= true;
break;
}
board[index]
= "";
}
}
if (done)
break;
}
// chECK IF COMPUTER CAN WIN
if (done) {
board[playIndex] = computerSymbol;
buildBoard(form);
alert("Computer Just
Won!");
clear(form);
} else {
// CAN'T WIN, chECK IF NEED
TO STOP A WIN
for (row = 1; row <=3;
row++) {
for (col = 1;
col <= 3; col++) {
index
= (row*10) + col;
if
(board[index] == "") {
board[index]
= playerSymbol;
if
(win(index)) {
playIndex
= index;
done
= true;
board[index]
= "";
break;
}
board[index]
= "";
}
}
if (done)
break;
}
// chECK IF DONE
if (done) {
board[playIndex]
= computerSymbol;
buildBoard(form);
} else {
// NOT DONE, chECK
FOR FIRST EMPTY SPACE
for (row = 1;
row <= 3; row ++) {
for
(col = 1; col <= 3; col ++) {
index
= (row*10) + col;
if
(board[index] == "") {
//chECK
ROW
if
(
((board[index] == board[(index < 30)?index+10:index-20])
&&
(board[(index>9)?index-10:index+20] == "")) ||
((board[index]
== board[(index>9)?index-10:index+20]) &&
(board[(index<30)?index+10:index-20]))
) {
playIndex
= index;
done
= true;
break;
}
//
chECK COLUMNS
if
(
((board[index] == board[(index%10<3)?index+1:index-2])
&&
(board[(index%10>1)?index-1:index+2] == "")) ||
((board[index] == board[(index%10>1)?index-1:index+2])
&&
(board[(index%10<3)?index+1:index-2]))
) {
playIndex
= index;
done
= true;
break;
}
}
}
if
(done)
break;
}
if (done) {
board[playIndex]
= computerSymbol;
buildBoard(form);
} else {
//
NOT DONE, chECK FOR FIRST EMPTY SPACE
for
(row = 1; row <= 3; row ++) {
for
(col = 1; col <= 3; col ++) {
index
= (row*10) + col;
if
(board[index] == "") {
playIndex
= index;
done
= true;
break;
}
}
if
(done)
break;
}
board[playIndex]
= computerSymbol;
buildBoard(form);
}
}
}
}
}
Note that this function checks only rows and columns
for possible good moves before going on the check for any empty
space. A good exercise would be to extend this function to also
check for diagonal moves before opting for the first available
blank space.