reptile7's JavaScript blog
Tuesday, May 09, 2006
 
Good Grief, More If...else
Blog Entry #38

We continue our if...else odyssey with a dissection of HTML Goodies' JavaScript Primers #22, "IF/ELSE Statements." Remember Primer #20's random number script? You know, the script that displayed a Date object-derived random number on an alert( ) box upon clicking a form button? Ah, good - Primer #22 brings back the Primer #20 Script, but with a twist: today, we'll use an if...else statement to turn this script into a guessing game that will "allow you to take pot shots [at the random number] until you get the number right," quoting Joe in the "Deconstructing the Script" section of Primer #22. With no further ado, let's put the Primer #22 Script in the crosshairs:

<html><head>
<script type="text/javascript">
function rand( ) {
now=new Date( );
num=(now.getSeconds( ))%10;
num=num+1; }
function guessnum( ) {
guess=prompt("Your guess?");
if (eval(guess) == num) {
alert("YOU GUESSED IT!!");
rand( ); }
else
alert("No. Try again."); }
</script></head>
<body onLoad="rand( );">
<h2>I'm thinking of a number from 1 to 10</h1>
<form name="myform">
<input type="button" value="Guess" name="b1" onClick="guessnum( );">
</form></body></html>

Alas, Joe's script demo does not work; however, the folks at Hope College's Computer Science Department have mirrored Primer #22 here, with a functional "The Script's Effect" page here.

We can break down the Primer #22 Script into three parts:

(1) The document head's rand( ) function, which is almost identical to the rand( ) function in the Primer #20 Script (not the Primer #21 Script, as incorrectly stated in the primer). Primer #20's rand( ) commands were discussed in detail in Blog Entry #36, so there's no need to rehash them here - we'll see below that these commands can be 'unfunctionized' (i.e., they don't need to be in a function) if desired;

(2) The document head's guessnum( ) function, which we'll take up shortly; and

(3) The document body HTML, which largely parallels the corresponding document body code of the last two primer scripts. (In case you were wondering, the "I'm thinking of a number from 1 to 10" header is uneventfully rendered as an h2 element by both MSIE and Netscape in spite of the mismatched closing </h1> tag, as can be shown at the aforelinked Hope College demo site, which does not bother to correct the mismatch.)

The guessnum( ) function

Let's set the stage, shall we? When the document loads, we first generate a random number num whose value runs from 1 to 10 via the rand( ) function, which is triggered by the onLoad event handler in the document <body> tag. We're now ready to guess what that random number is, and we accordingly click the "Guess [the number]" button, which triggers the guessnum( ) function.

Up pops a prompt( ) box that asks, "Your guess?" The user types in a number, which is assigned to the variable guess after the "OK" button is clicked. This brings us to the script's central if...else statement:

if (eval(guess) == num)
{alert("YOU GUESSED IT!!"); rand( );}
else
alert("No. Try again.");

The if code is executed if the user guesses the random number correctly; for an incorrect guess, the else code is executed.

Without any comment at all on Joe's part, the if conditional statement invokes the top-level eval( ) function, whose purpose here is to ensure that guess is interpreted as a number data type and not as a string data type (recall from the previous post that prompt( ) outputs are strings). The eval( ) function evaluates code in the form of a string; for example, eval("31%10") would return 1, and with respect to the Primer #22 Script, eval("document.myform.b1.type") would return button.

(There's no mention of the eval( ) function on Primer #22's "What You've Learned" page, but it does at least appear in the HTML Goodies JavaScript methods keyword reference.)

As noted in Netscape's eval( ) documentation, the eval( ) function evaluates numerical string literals to give their number equivalents. So, getting back to our guessing game, suppose the user types 5 into the prompt( ) box; 5 is outputted/assigned to guess as a string (i.e., it's now "5"), and then eval(guess)=eval("5") reconverts "5" to 5, the number.

The if statement then compares the eval(guess) return with the value of num from the rand( ) function to see if they are equal; this comparison raises a few interesting-if-not-quite-crucial issues:

(1) With respect to variable scope, both the now and num variables are initialized inside of the rand( ) function and thus qualify as local (not global) variables; before the fact, then, one might expect the guessnum( ) function to not recognize num and perhaps throw an error at this point, and yet it is clear that num undergoes 'function crossing' without any problems, as you can see for yourself at the Hope College demo page.

(2) Netscape's "Variable Scope" documentation also states, "[Y]ou must use var to declare a variable inside a function." Neither now, num, nor guess is declared via the var keyword, but this again poses no problems vis-à-vis the script's effect. (Looking back, I see that the assignment answer scripts for Primers #14, #20, and #21 also feature local variables that are not declared with var.)

(3) If we're going to use the == equal comparison operator here, then use of the eval( ) function is in fact not necessary. According to Netscape's "Comparison Operators" documentation: "The standard equality operators (== and !=) compare two operands without regard to their type...When type conversion is needed, JavaScript converts String, Number, Boolean, or Object operands as follows[:] When comparing a number and a string, the string is converted to a number value." It follows that in the eval( )-less statement:

if (guess == num) {alert("YOU GUESSED IT!!"); rand( );}

the browser's JavaScript engine will automatically convert guess to a number, so no 'evaluation' of guess is required.

(The preceding Netscape reference notes that JavaScript also has a === "strict equal" binary comparison operator that returns true only if its operands are equal and of the same type; the use of === in guessnum( )'s if statement:

if (eval(guess) === num) {alert("YOU GUESSED IT!!"); rand( );}

would indeed require the eval( ) function to convert guess to a number.)

Anyway, if eval(guess) and num are equal, then the if alert( ) box pops up announcing "YOU GUESSED IT!!", followed by a rand( ) function call, which Joe does not discuss and which generates a new random number if the user wants another go at the game. As far as I am aware, this is the first time in the HTML Goodies JavaScript Primers series that a function is called via a basic function_name( ) command (as opposed to using an event handler).

If eval(guess) and num are not equal, then the else alert( ) box pops up announcing "No. Try again." To make subsequent guesses, the user reclicks the "Guess [the number]" button and the guessnum( ) process starts all over again.

Note that alert("No. Try again."); is not surrounded by braces, which can be omitted for a solitary if or else command.

Other code possibilities

As noted above, the random number code does not need to be functionized. Consider, for example, the following script:

<head><script type="text/javascript">
now=new Date( ); num=(now.getSeconds( ))%10; num=num+1;
function guessnum2( ) {
var guess=prompt("Your guess?");
if (guess == num) {alert("YOU GUESSED IT!!"); history.go(0);}
else {alert("No. Try again."); guessnum2( );} }
</script></head>
<body><h2>I'm thinking of a number from 1 to 10</h2>
<form><input type="button" value="Guess the number" onclick="guessnum2( );">
</form></body>

Try it out:


After a first play of the game, the random number is refreshed by reloading the page via a history.go(0) command in the if code. I've also included a guessnum2( ) function call* in the else code - this sets up a prompt( )-alert( ) loop** for incorrect guesses so that you don't need to click the "Guess the number" button over and over.
(*Netscape's "Calling Functions" documentation notes, "A function can...be recursive, that is, it can call itself.")
(**I recognize that some users might find this annoying; the else guessnum2( ) function call can be left out if desired.)

Relatedly, the random number code can alternatively be brought into the guessnum( ) function if, again, guessnum( ) includes a looping mechanism that bypasses the random number code; for example:

<head><script type="text/javascript">
function guessnum3( ) {
now=new Date( ); num=(now.getSeconds( ))%10; num=num+1;
guess=prompt("Your guess?");
if (guess == num) {alert("YOU GUESSED IT!!");}
while (guess != num) {
alert("No. Try again.");
guess=prompt("Your guess?");
if (guess == num) {alert("YOU GUESSED IT!!");} } }
</script></head>
<body><h2>I'm thinking of a number from 1 to 10</h2>
<form><input type="button" value="Guess the number" onclick="guessnum3( );">
</form></body>

The script above uses a while loop to handle incorrect guesses:

while (guess != num) {
alert("No. Try again.");
guess=prompt("Your guess?");
if (guess == num) {alert("YOU GUESSED IT!!");} }

The while loop code says, "As long as guess is not equal to num [!= is the 'not equal' comparison operator], then execute the following code in {braces} repeatedly, but if guess becomes equal to num, then stop"; note that the browser skips over the loop's if statement if guess is not equal to num. We will revisit the while loop topic when we discuss Primer #25.

The Primer #22 Assignment

In the Primer #22 Assignment, Joe asks the reader to modify the Primer #22 Script so that it indicates whether the user's guess at the random number is too high, too low, or just right. Towards this end, the assignment answer script employs in a corresponding guessnum( ) function a series of if statements making use of the ==, > (is greater than), and < (is less than) comparison operators:

function guessnum( ) {
guess=prompt("What is your guess?");
if (eval(guess) == num) // if statement #1
{alert("YOU GUESSED MY NUMBER!!");}
if (eval(guess) > num) // if statement #2
{alert("Too high! Press the button to guess again.")}
if (eval(guess) < num) // if statement #3
{alert("Too low! Press the button to guess again.")} }

In none of the if statements above is the eval( ) function actually necessary to convert guess to a number; > and < comparisons are like == comparisons in this regard.

Significantly, note that if statement #1 (the "just right" comparison) does not have a rand( ) function call for renewing the random number num, and there's a reason for this: if it did include a rand( ); command, then when the user guesses correctly, the "YOU GUESSED MY NUMBER!!" alert( ) message will in most cases be followed by either a "Too high!" or a "Too low!" alert( ) message. Bear in mind that even if if statement #1's condition is true, the browser isn't finished with the guessnum( ) function and is still going to check if statements #2 and #3. Evidently and somewhat unintuitively, a rand( ) function call in if statement #1 creates a new random number num, ready for use, before the browser acts on if statement #2, and if the new num is different than the old num, then either if statement #2's condition or if statement #3's condition will be true, resulting in a second alert( ) message.

There are at least three simple workarounds for this problem:
(1) Renew the random number num with a history.go(0) command in if statement #1, as in the guessnum2( ) function above;
(2) Put a rand( ) function call in if statement #1 but delay its execution via the setTimeout( ) method of the window object, e.g., window.setTimeout("rand( )",2000); or
(3) Reorder the guessnum( ) if statements, putting the "just right" comparison last.

Finally, the assignment does make a useful larger point: a 'multiple-choice' script leading to three or more outcomes can often be crafted with a set of if statements and without any else statements.

We'll conclude our if...else series in the next post with a discussion of HTML Goodies' JavaScript Primers #23, in which we'll use if...else statements to display random text strings and images.

reptile7

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)