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 only gets 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 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.
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.
Input
<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>
Output
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.
Analysis
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 not only used 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, it is important to remember that each property in an object can be referred to by a numberits 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 9each 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 of 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.
Input
<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 if the given character is a numeric value. If not, you set isNum to false. After the loop, you return the value of isNum.
We check each character to see if 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 was 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; }
End of Analysis
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 towards 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.
Input
<HTML> <HEAD> <TITLE>Listing 7.3</TITLE> <SCRIPT LANGUAGE="JavaScript"> function checkNum(toCheck) { var isNum = true; if ((toCheck == nul) || (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" [ic:ccc]onChange="checkFieldNum(this);"><BR> Speed of Person B: <INPUT TYPE=text NAME="speedB" [ic:ccc]onChange="checkFieldNum(this);"><BR> <INPUT TYPE=button Name="Calculate" VALUE="Calculate" [ic:ccc]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>
Output
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.
End of Output
Analysis
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 if 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; } }
You could even make this function easier by adding a for or for ... in loop:
function assign(form) { with (form) { for (j = 0; j < length; j++) { elements[j].value = j; } } }
Here the loop cycles from 0 to one less than the value of the property form.length. form.length contains the number of elements in the form. The indexes of these elements in the elements[] array are from zero to length-1.
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 impliesit 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 easily implement the game, 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.
Input
<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 clear(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!"); 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; board[index] = ""; 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] == "") { 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="clear(this.form);"> </FORM> </BODY> </HTML>
Output
This script produces results similar to those in Figure 7.4.
Figure 7.4. This tic-tac-toe game makes extensive use of loops.
End of Ouput
Analysis
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 better understand 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 ten 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 some 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 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!"); clear(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 only breaks out of the innermost loop, 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!"); clear(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 only missing 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 which 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 by another set of embedded for loops and breaking 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 if the field the user has selected is empty. If not, you remove the focus immediately so that the user is only free to play in empty fields 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.
End of Analysis
Now that you have a firm grasp on the concept of loops, you can look at how for loops can be used to 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.
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, it is possible to 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 |
|
|
|
Q: Can 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.
It is important to note that this function only checks 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.