Полезная информация

Chapter 7

Loops


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:

Loops-Basic Concepts

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 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.

The for ... in Loop

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.

Using Loops to Check for Numbers

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;
    }

The while Loop

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;
  }
}

The break and continue Statements

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.

Creating a Game of Tic-Tac-Toe

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.

Creating Arrays with for Loops

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.

Summary

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.

Commands and Extensions Review

Command/ExtensionType Description
for StatementLoops based on an initial value, a condition, and an expression
for ... in StatementLoops through all the properties in an object, returning the index of the property
while StatementLoops based on a condition; continues until the condition is false
with StatementEnables a command block to omit an object prefix
break StatementBreaks out of the current loop
continue StatementJumps to the next iteration of the current loop

Q&A

Qan I use the same variable as the counter for more than one loop?
AYes. 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.
QI've seen a repeat ... until loop in some other programming languages. Does JavaScript have one?
ANo. 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.

Exercises

  1. 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;
    }
  2. 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.
  3. 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.

Answers

  1. 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;
        }
  2. 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;
    }
  3. 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.