The Power Of ON-GOTO And ON-GOSUB
Ronald R. Lambert
This tutorial details the use of ON-GOTO and ON-GOSUB, two of the most powerful statements in BASIC. Although the program examples are written in Atari BASIC, the principles apply to most microcomputers with BASIC.
ON-GOTO and ON-GOSUB are among the last commands learned by many new BASIC programmers. Even intermediate-level programmers may pass them up because the same results can be achieved by IF-THEN statements and logical comparisons. But the power of ON-GOTO and ON-GOSUB is appreciable. Once you learn how to use these commands, you'll wonder how you got along without them for so long.
The ON commands are used in program branching in cases where the value of some expression (a variable, calculation, and so on) determines the program line where execution goes next. This seems straightforward enough; but there is more to these commands than meets the eye. We'll look at how to handle these commands in Atari BASIC, but the examples will work with little or no modification in almost any version of BASIC.
Multiple Branches With ON-GOSUB
Suppose you are writing a game program, and you want to move a shape in one of four directions around the screen. You might, typically, convert the keyboard input so that if a cursor key is pressed, the program assigns a variable different values representing the corresponding direction. The variable may be set to 1 when you press cursor up, 4 when you press cursor right, and so forth. Similarly, you might convert joystick input to a series of numeric values indicating directions.
In this case, you might have a separate subroutine to handle movement in each direction. A single ON-GOSUB statement can handle all the branches, directing execution to the proper subroutine. Here is a short program that demonstrates the multibranching feature of ON-GOSUB. When it prompts you for a number, enter a value from 1 to 4.
10 PRINT "ENTER NUMBER : "; : INPUT D 20 PRINT "D = "; D; " : "; 30 ON D GOSUB 40, 50, 60, 70 : GOSUB 80 : GOTO 10 40 PRINT "CAME TO LINE 40 : "; : RETURN 50 PRINT "CAME TO LINE 50 : " : RETURN 60 PRINT "CAME TO LINE 60 : "; : RETURN 70 PRINT "CAME TO LINE 70 : "; : RETURN 80 PRINT "CAME TO LINE 80 : "; : RETURN
When you run this program, it asks you to enter a number. If you enter 1, the program displays the message Came to line 40. After it returns from the subroutine at line 40, notice that the program does not proceed to the routines in lines 40–70. Instead, it proceeds to the next BASIC statement in the line, GOSUB 80. This statement is always performed, allowing you to note the difference between ON-GOSUB and a normal GOSUB command.
Now try entering a number smaller than 1 or greater than 4. Notice how ON-GOSUB responds. In this case, the program skips all of the possible destination lines listed in the ON-GOSUB statement. ON-GOSUB does not cause any branches, and the program proceeds to the next statement (GOSUB 80). For example, if you enter 0 at the prompt, the program prints Came to line 80.
The Destination List
The program demonstrates some basic features which ON-GOTO and ON-GOSUB share. Both statements are followed by a list of destination line numbers. The first line number is used when the tested expression equals 1; the second line number is used when the expression equals 2, and so on. If the expression evaluates to zero, no branch is taken, and the program proceeds to the next statement.
In the simple example above, the expected number series 1-2-3-4 corresponds neatly to the needs of our program. In other cases, you might have an expression that doesn't evaluate in 1-2-3 order. Say, for instance, that your expression might produce the values 1, 3, 4, and 5 (the value 2 is missing). Atari BASIC does not allow you to simply omit a line number from the destination list, although that does work in some other versions of BASIC. (The statement ON X GOSUB 100, , 300, 400, 500 causes a syntax error in Atari BASIC, but it works in most other BASICs.) One solution is to create a do-nothing subroutine that consists of nothing but a RETURN statement. In the unlikely event that your program ever produces the unexpected value, the program will return without doing anything. For example, including the line 200 RETURN allows you safely to use the statement ON X GOSUB 100, 200, 300, 400, 500.
Branching With ON-GOTO
You can make multiple branches in much the same way with ON-GOTO. To see how this works, substitute GOTO for each GOSUB in line 30 of the example program, and replace each RETURN in lines 40-80 with the statement GOTO 10.
When you run the program with these changes, it branches to the same destinations as before, except that it never reaches line 80 unless you enter a value outside the range 1-4. The last command in line 30 (GOTO 10) is rendered superfluous and may be deleted.
The real difference, of course, between ON-GOSUB and ON-GOTO is in what happens after the branch occurs. In the case of ON-GOSUB, the program returns to the next BASIC statement after the ON-GOSUB statement when a RETURN is encountered. On the other hand, an ON-GOTO statement, like a regular GOTO, doesn't automatically return to the part of the program where it originated.
Priority Sifting
The basic use for ON-GOSUB and ON-GOTO, then, is to test a range of conditions and branch to any of several possible destinations. In the previous example, the condition was the value assigned to the variable D, and the range was a series of numbers from 1–4, inclusive. In addition to such basic uses, ON-GOTO and ON-GOSUB can be employed to make a series of decisions in order of priority—a technique which we'll explain with a practical example.
Suppose that you are writing a text-adventure program. In an adventure, or other simulation of real-life environments, one of the most basic problems is how to assign different properties to objects and discriminate intelligently among them. For example, say that you, as the hero or heroine of the adventure, try to pick up a castle and stuff it into a gunny sack containing other objects. To respond to this action, the program must first be able to tell what properties the castle possesses. You don't want to print an inappropriate message like You must drop something else before picking up the castle, because that would imply that the castle is something that can be picked up.
In a typical adventure program, an object might have any of several different properties. Some objects can be picked up, but others are immovable; some are valuable, while others are useless; some are dangerous; some can be eaten or drunk; some possess magical powers, and so forth. Certain properties exclude others, as well. If you can't pick up a castle, then it's not something which you can eat or use as a weapon, for instance. To deal sensibly with this wide range of possibilities, the program needs a subroutine that will consider certain factors in order of precedence and respond as fits the situation.
ON-GOTO and ON-GOSUB are ideally suited for this sort of work. While you could accomplish the same task with a series of IF-THEN commands, see how neatly and concisely ON-GOTO performs these tasks in the following example. In a text adventure, the program might use this routine whenever you try to pick something up.
700 TAKE = 0 : ON IMMOVE GOTO 710 : ON WEIGHT>STRENGTH GOTO 720 : ON BURDEN>CAPACITY GOTO 730 : TAKE = 1 : RETURN 710 PRINT "YOU CAN'T PICK UP A "; OBJECT$; "!" : RETURN 720 PRINT "THAT'S TOO HEAVY FOR YOU."; : ON NOT INJURED GOTO 740 : PRINT CHR$ (126); " IN YOUR PRESENT CONDITION." : RETURN 730 PRINT "YOU CAN'T CARRY ALL THAT. YOU'LL HAVE TO DROP SOMETHING."; 740 PRINT : RETURN
The variable IMMOVE shows whether an object can be picked up; IMMOVE equals 0 if an object is movable or 1 if it's not. Let's assume that the program sets IMMOVE appropriately before we enter this routine. If you try to pick up a castle, IMMOVE equals 1 when the program executes line 700. The first ON-GOTO statement in line 700 responds to that condition, causing a branch to line 710, which prints You can't pick up a castle and returns. On the other hand, if you decide to pick up something more sensible, such as a goblet or newspaper, IMMOVE will be zero, in which case no branch is taken at the first ON-GOTO.
At this point, we have established the most basic fact needed for a response—whether the object can be picked up at all. If an object can't be carried, then we needn't waste time deciding other questions such as whether you have sufficient strength to lift this particular object, or whether you already are carrying too many other things.
Let's assume that you pick up something sensible and pass the first ON-GOTO test. The next statement in line 700 happens to be another ON-GOTO statement which compares the weight of the object to your current strength, to determine whether you have enough vigor to grab it. Again, we'll assume that the variables WEIGHT and STRENGTH get assigned elsewhere in the program.
If you fail the strength test, the ON-GOTO statement diverts execution to line 720, which prints the message That's too heavy for you. This line also contains a secondary ON-GOTO which checks the variable INJURY to see if an injury is responsible for your inability to take the object. If so, the program prints in your present condition at the end of the last message. Notice the use of the logical operator NOT in this comparison. If the variable INJURED equals zero, the program branches to line 740, which simply prints a blank line and exits the routine with RETURN. (Note that the result of the NOT operator may differ in other versions of BASIC.)
At this point, we have passed the second level of discrimination; we know that the object is both movable and light enough to pick up. The third test in line 700 tests whether you already are carrying so many objects that you can't pick up another, comparing the variables BURDEN and CAPACITY. We'll say that BURDEN represents the size of your current load, and CAPACITY indicates the maximum number of objects you can carry.
If your current burden exceeds the maximum capacity, the last ON-GOTO statement causes a branch to line 730, which advises you to drop something else before trying to grab the desired object. Only if all three conditions are met does the program allow you to pick up the object and add it to your inventory.
Notice how the logical tests are arranged in order of priority, so that we establish the most basic facts first. If the object is immovable, then there's no point in checking whether you can lift it or have room in your sack to stow it—and it would diminish the realism of the adventure to print messages about those secondary topics. Similarly, if you are too weak or injured to pick something up, then we don't care how much room is in your sack. This is quite different from the joystick-reading example mentioned earlier, where all of the possible program branches have equal weight. The ability to sift through a series of tests in order of priority is essential to complex decision making.
Emulating IF-THEN-ELSE
If you're familiar with other versions of BASIC, you may have noticed something about the ON-GOTO statements in the previous example. They operate something like an IF-THEN-ELSE structure, in that the computer continues to read additional statements in the same program line if the conditions for the first ON-GOTO are not satisfied.
In most versions of BASIC, program execution drops down to the next program line whenever the conditions for an IF statement are not fulfilled. But many newer versions of BASIC allow you to add an ELSE command to an IF-THEN structure on the same program line, so that in the event the first IF test fails, the computer goes on to perform the statements that appear after ELSE. When ELSE is followed by an additional IF-THEN construct (which may include another ELSE, in turn), you can create a single line that performs quite sophisticated logical tests.
While Atari BASIC does not support the ELSE statement, you can use ON-GOTO and ON-GOSUB to much the same effect. Even though these commands can only result in a decision whether or not to branch, they can be followed on the same program line by other BASIC statements. This is demonstrated in lines 720 and 740 of the previous example. The ON-GOTO statement in line 720 has exactly the same effect as an IF-THEN-ELSE statement.
Series Comparisons
Yet another use for ON-GOTO or ON-GOSUB is for making a series of logical comparisons, which would otherwise require a FOR-NEXT loop containing one or more IF-THEN statements. This is demonstrated in the following program. Despite its brevity, this is a complete program, which converts into Arabic equivalents Roman numerals you have entered.
10 DIM RN$(120), C$(1), R(6) : PRINT CHR$(125) 20 R(0) = 1 : R(1) = 5 : R(2) = 10 : R(3) = 50 : R(4) = 100 : R(5) = 500 : R(6) = 1000 30 PRINT : PRINT "ENTER ROMAN NUMERALS : "; : INPUT RN$ 40 Z = LEN(RN$) : IF Z = 0 THEN 30 50 TRAP 110 : L = 0 : S = 0 60 FOR X = 1 TO Z : RESTORE 100 : Y = -1 70 Y = Y + 1 : READ C$ : ON C$<>RN$ (X, X) GOTO 70 80 N = R(Y) : S = S + L*(L> = N) -L* (L<N) : L = N 90 NEXT X : PRINT "ARABIC EQUIVALENT : " : S + N : GOTO 30 100 DATA I, V, X, L, C, D, M 110 PRINT CHR$ (253) : "ILLEGAL CHARACTER : ";RN$(X, X) : GOTO 30
The heart of this program is the ON-GOTO command in line 70. Each Roman numeral you enter is compared to the legal characters in the DATA statement. As long as the characters don't match, the ON-GOTO command loops back to the beginning of line 70 to READ the next character from the DATA list. When there is a match, program execution proceeds to the next statement after the ON-GOTO, adding the value assigned to that numeral to the cumulative sum for the whole number.
There are two advantages to using ON-GOTO in this situation instead of a FOR-NEXT loop. First, the loop terminates as soon as the tested condition is met—no time is wasted performing meaningless tests while a FOR-NEXT loop completes its appointed rounds. (It is possible, of course, to exit a FOR-NEXT loop prematurely, but this a dangerous programming practice, since you may overflow the computer's stack and cause an error if you jump out of too many loops without performing the right number of POPs.)
Incidentally, the formula used in this program to compute Arabic equivalents for Roman numerals is a little different from the one you may have learned in school. Instead of subtracting a smaller number from a following larger number (as in IV, XL, and so on), the program subtracts the smaller number from the cummulative sum when it is followed by a larger number, and then adds the larger number. The results are the same in both cases.
Other Creative Uses
The final demonstration program exhibits a number of creative uses of ON-GOTO and ON-GOSUB. The program asks you to enter a date, and it then tells you what day of the week that date falls on. For instance, if you enter JULY followed by 4 followed by 1776, the program prints the message JULY 4, 1776 is a Thursday. To keep the code reasonably short, its responses are limited to dates following the calendar reform of 1752, when the modern Gregorian calendar was adopted in the English-speaking world.
This 1752 limit on date calculations is the result of the Gregorian calendar reform: To bring the calendar back into harmony with sun time so that the equinoxes and solstices would fall on their traditional dates, the day Wednesday, September 2, 1752 was followed by Thursday, September 14, 1752. As a result, computations that take the modern calendar back prior to that date will produce a false weekday result unless you make a specific adjustment. (The year 1752 is when England and its colonies, including the ones which later became the United States, made the change. Many European countries made the change earlier, beginning in 1582 when the Gregorian calendar system was introduced. Pope Gregory decreed that Thursday, October 4, 1582 was to be followed by Friday, October 15, 1582. For countries where the change was made on the original date, change the 639797 in line 110 to 577736.)
The same calendar reform changed the formula for calculating leap years. Under the modern Gregorian calendar, centenary years (years evenly divisible by 100) are not leap years unless they are also evenly divisible by 400. This creates the interesting category of leap centuries. This explains the length of the formula in line 90 for determining leap years.
Observe carefully how this program performs its functions, especially the ON-GOTO and ON-GOSUB commands. This example may suggest additional ways to use these commands in your own programs.
Calendar
For instructions on entering this program, please refer to "COMPUTE!'s Guide to Typing In Programs" elsewhere in this issue.
OB 10 DIM MONTH$(9), DAYNAME$(6), MO$(2), M(12) EN 20 M(0) = 0 : X = 0 : PRINT CHR$(125) BG 30 X = X + 1 : ON M(X-1)<>31 GOSUB 50 : ON M(X-1) = 31 GOSUB 40 : ON X = 8 GOSUB 50 : ON X<12 GOTO 30 : GOTO 60 BE 40 M(X) = 30 : REUTURN BG 50 M(X) = 31 : RETURN LH 60 PRINT : PRINT "Month name : "; : INPUT MONTH$ : TRAP 60 : PRINT "Day : "; : INPUT DAY : PRINT "Year : "; : INPUT YEAR CE 70 RESTORE 190 : X = 0 MB 80 X = X + 1 : ON X>12 GOTO 160 : READ MO$ : ON MO$""MONTH$ (2, 3) GOTO 80 : M = X BO 90 LEAP = (INT(YEAR/4) = YEAR/4) - (INT(YEAR/100) = YEAR/100) + INT(YEAR/400) = YEAR/400) : M(2) = 28 + LEAP AB 100 ON DAY>M(M) GOTO 170 AH 110 DAYS = INT((YEAR-1)*365.2425) : FOR X = 0 TO M-1 : DAYS = DAYS + M(X) : NEXT X : DAYS = DAYS + DAY : IF DAYS<639797 THEN 180 EI 120 RESTORE 200 : X = 0 : WEEKDAY = DAYS-INT (DAYS/7)*7 : IF WEEKDAY = 0 THEN WEEKDAY = 7 JI 130 X = X + 1 : READ DAYNAME$ : ON X<WEEKDAY GOTO 130 IA 140 PRINT MONTH$;" "; DAY; ", "; YEAR;" is a "; DAYNAME$; "day." DF 150 GOTO 60 CM 160 PRINT "What month is "; MONTH$;"?" : ZANY = ZANY + 1 : ON ZANY + 1 : ON ZANY<2 GOTO 60 : PRINT "Let's get serious!" : GOTO 60 AC 170 PRINT MONTH$; " only has "; M(M);" days." : ON M<>2 OR DAY<>29 GOTO 60 : PRINT YEAR;" is not a leap year." : GOTO 60 CL 180 PRINT : PRINT "This date was prior to the adoption ofthe modern (Gregorian) calendar. " : Goto 60 LG 190 Data AN, EB, AR, PR, AY, UN, UL, UG, EP, CT, OV, EC GB 200 DATA Mon, Tues, Wednes, Thurs, Fri, Satur, Sun