Amiga BASIC StyleJim Butterfield, Associate Editor
Here's how to manage custom menus and output windows, read mouse input, trap background events, and master other techniques which give Amiga BASIC its unique character. The article also highlights some of the differences between Amiga BASIC and earlier BASICs, and includes a useful program for calculating mortgages.
There's a different style to BASIC programming on the Amiga. You should take a close look at new features; you'll discover concepts that lead to a radically different style of programming and user interaction.
To illustrate some of these concepts, let's construct a simple Amiga BASIC program which analyzes the five important variables in a home mortgage: principal (amount borrowed), interest rate, period of loan, monthly payment, and balance due. Since interest compounding schedules are different in Canada than in the United States, the program includes an option for choosing either schedule. We'll discuss elements of the program as we go through it.
[Editor's note: In the following listing we have used the « character to indicate the end of a program line. Don't try to type this character-we've deliberately chosen one that's not on the Amiga keyboard. The « character merely shows where you should press RETURN to end one program line and start another.]
REM Mortgage (Version 1)« DIM title$(6),site$(2),pudef$(5),value#(5),peryear(2),compound(2)« cal=4:site=1«
The REM identifies the program and version. The DIM statement defines the six arrays used in the program, which we'll discuss as we go along. Note that there are no line numbers in Amiga BASIC. They are not needed. Even with GOTO or GOSUB, it's usual to identify a line with a label, not a number. (You may include line numbers if you like-a feature included for the sake of compatibility with other BASICs-but since the line numbers are treated simply as labels, numeric order is irrelevant.)
Also, notice that we use descriptive words for variable names. In the versions of BASIC on earlier Commodore computers, only the first two characters of the variable name were significant (H0$ and HOUSEHOLD$ would be considered the same name). In Amiga BASIC, names can be up to 40 characters long with every character significant (Householdbudget1 and Householdbudget2 are recognized as distinct names). Descriptive variable names make the program much easier to understand and reduce the need for explanatory REM statements. We also set the default value of the two variables that determine which menu items are selected. The loan variable to be calculated (cal) is 4, the payment amount. The default interest compounding schedule (site) is that for country 1, Canada. Change either of these if you wish.
DATA Principal, Rate, Years ,Payment, Balance, Quit« MENU 5,0,1, "Calculate"« FOR j=1 TO 6:READ title$(j)« MENU 5,j,1-(j=cal)," "+title$(j):NEXT j«
The DATA statement contains the items for the first of our custom menus, as well as the captions for the output window (the array title$). One of the most significant features of Amiga BASIC is that the programmer can easily construct custom menus.
We'll choose menu 5 for our first custom menu so that menus 1-4 can retain their default uses: Project, Edit, Run, and Windows. The first MENU statement sets Calculate as the title for the menu, then the FOR-NEXT loop reads the DATA items into the corresponding menu slots. Note the expression 1-(j=cal) for the third parameter of the MENU statement in the loop. Just like earlier Commodore BASICs, Amiga BASIC interprets a true expression as -1 and a false expression as 0, so 1-(j=cal) will evaluate to 1-(-1) = 2 when the value of j equals the value of cal, and 1-(0) = 1, otherwise. A value of 2 for this parameter puts a check to the left of the menu item, so this feature is used to indicate which calculation option is currently selected. A value of 1 displays the menu item without a checkmark, but still makes it active; a value of 0 would deactivate the menu item. leaving it dimmed, or ghosted, and impossible to select.
DATA Canada,2,6,USA,12,1« MENU 6,0,1,"Country"« FOR j=1 TO 2:READ site$(j),peryear(j),compound(j)« MENU 6,j,1-(j=site)," "+site$(j):NEXT j«
Different rules are used in the U.S. and Canada to work out a monthly interest rate based on the annual interest figure. In the U.S., the annual amount is simply divided by 12. In Canada, semiannual compounding is used, which involves dividing by two to get the semiannual rate and then using a more complex formula. The user will be able to pick the appropriate system from menu 6, which is titled Country. It would not be too hard to add extra menu items, such as compounding quarterly (the numeric DATA items would be 4,3). The FOR-NEXT loop here uses the same technique for flagging the current menu selection as the one above.
Format With PRINT USING
DATA "#,###,###.##"« DATA " ###.###% "« DATA " ###.### "« DATA "#,###,###.##"« DATA "#,###,###.##"« FOR j=1 TO 5:READ pudef$(j): NEXT«
These are the PRINT USING templates that tell how the numeric values of the five loan variables are to be printed. The principal amount, for example, is printed as a dollars-and-cents value. The annual interest rate, in contrast, will be shown to three decimal places with a percent sign.
DATA 10000,10,10,0,0« FOR j=1 TO 5:READ value#(j):NEXT j«
These are just arbitrary figures to appear on the initial screen. I've picked a principal amount of $10,000 at 10 percent over ten years. You could substitute your own default values if you like. Once the program is running, any of these values can easily be changed.
An important point: Note that the array into which the values are read, value# has an extra symbol at the end. The # sign (pound sign, hash mark, or whatever you want to call it) indicates that these variables are double precision. If you've worked with previous Commodore machines which offered only one level of numeric precision, you might be unclear about this issue. Here's the story: In earlier Commodore BASICS, variables worked to about ten digits of accuracy. That was enough-just barely enough-to do most home finance calculations. Normal (single-precision) Amiga BASIC variables-the type you usually get if you don't add a type identifier after the variable name-are reliable to only about seven digits. This means that it can't handle amounts of over about $167,000 without losing pennies.
Computer scientists will tell you that single-precision Amiga BASIC variables have a 24-bit mantissa, as opposed to the 32-bit mantissa in earlier Commodore BASICs. What it means to you is this: Whenever you need to deal with dollars-and-cents values-or with other values requiring a high accuracy-you need to call for a double-precision variable. Such a variable will have more accuracy-enough to cover a federal budget and still be exact on the pennies. To specify double precision, add a # sign to the end of the variable name. Be careful to include the sign each time you use the variable name, however. Amiga BASIC will consider value and value# to be two different variables.
A Custom Window
WINDOW 2, "Mortgage",(10,10)-(400,100),8« WINDOW OUTPUT 2« GOSUB calc:GOSUB showval« LOCATE 7,1« PRINT "Use menu buttons to select option."« PRINT "Click on existing values to change."« GOSUB hang« WINDOW CLOSE 2« END«
Now we open a new window in which the calculations will appear. The only gadget we put on the window is the closing gadget (code 8). It's there so that the user can still put away the window manually in case the program is stopped. The window is not only created, but also selected for output. Then the initial calculations are displayed, along with brief instructions near the bottom of the window.
The program's main job is a subroutine called hang. We'll stay in that subroutine until the user wants to quit, at which time the window will be closed. Here is the hang subroutine:
hang:« ON MENU GOSUB event« ON MOUSE GOSUB event« MOUSE ON« MENU ON« kwit=0« WHILE kwit<>1:WEND« MOUSE OFF« MENU OFF« MENU RESET« RETURN«
We define an action for the mouse and for the menus we previously defined. Clicking the left mouse button or selecting a menu item invokes the event subroutine. These two activities are interrupts or event traps. After they are activated with MENU ON and MOUSE ON, they will remain in place, waiting for the appropriate event to happen, until they are canceled or turned off. While they are active, it doesn't matter what the program is doing; a suitable stimulus will immediately cause the program to jump to the specified subroutine.
A variable called kwit is used by the program to tell when it's time to quit. As long as it's zero, the program stays in the WHILE-WEND loop. How does it ever get out of this seemingly endless loop? Remember the event traps we just enabled. Pressing the left mouse button or selecting a menu item will trigger a GOSUB to the event routine, which in turn calls subroutines to process the button click or menu selection. One menu selection, the Quit option from the Calculate menu, will change the value of kwit to one to end the loop. After exiting the loop, we'll shut off the menu and mouse, disconnect the event traps, and return to the main program which ties things up.
A Major Event
event:« ms=MOUSE(0):mn=MENU(0)« IF mn THEN GOSUB menuhit« IF ms THEN GOSUB eek« IF kwit=0 THEN GOSUB calc:GOSUB showval« RETURN«Now let's look at the routine where the real action takes place. When we arrive at the event subroutine, we know that one of two things has happened. Either the left mouse button has been clicked or a menu item has been selected by using the right mouse button. The MOUSE and MENU functions are used to check which, and the appropriate service subroutine is called. Once the new value for cal or site has been established, we're ready to calculate new values, but first we check that kwit is still zero-we don't want to calculate values if the Quit option from the Calculate menu was selected. The new financial values are determined by calling the subroutine calc, then displayed using the showval subroutine. Keep in mind that we'll come back to this routine to recalculate anytime the data elements-or the rules-are changed.
calc:« ON ERROR GOTO oops« principal#=value#(1)« r1#=(value#(2)/100/peryear(site)+1)^(1/compound(site))« rate#=r1#-1« months=value#(3)*12« payment#=value#(4)« balance#=value#(5)« ON cal GOSUB fprin,fintr,fper,fpay,fbal« scale=100:IF cal=2 OR cal=3 THEN scale=1000« value#(cal)=INT(value#(cal)*scale+.99)/scale« ON ERROR GOTO 0« RETURN«
The calc subroutine is where the dirty work begins. The principal, interest rate, number of periods, payment amount, and final balance are extracted from the value# array so that they can be used by the various calculation programs more easily. Note that in most cases, we retain double-precision accuracy with the # sign. The monthly interest rate is worked out by a fairly complex formula, and the number of months equals the number of years times 12.
The variable cal tells us what to calculate. Depending on its value, we'll call fprin (find principal), fintr (find interest rate), fper (find period), fpay (find payment), or fbal (find balance). The calculation with scale rounds any calculated value to the next highest penny, or, if not a money figure, to three decimal places.
The calculation subroutine also includes an error trap, since some calculations are impossible or ridiculous (for example, how long would it take to pay off a $1,000 mortgage with a payment of $0 per month?). Problems are directed to an event trap named oops.
oops:« value#(cal)=0« RESUME oops2« oops2:« WINDOW 2« RETURN«
If there's any calculation problem, we set the calculated value to zero and give up. We do not go back to the detailed calculation program. Instead, using oops2, we return to the main calc routine. But, first, it's necessary to reopen WINDOW 2, since the Amiga always closes any secondary windows when an error occurs. Notice that the message at the bottom of the window is not reprinted. So if you see the window blink, then reappear minus the message and with the value being calculated set to zero, an error has been trapped. If this occurs when you enter what seem to be legitimate values, it may indicate that you made an error while entering the program. For this reason you may want to omit the ON ERROR statements until you are confident that you have eliminated all typing mistakes in the program.
Here are the five calculation routines. We won't plunge into details of the math here, since it's rather complex.
fprin:« value#(1)=(balance#+payment#*(r1#^months-1)/rate#)/r1#^months« RETURN« « fintr:« r0#=0:r1#=EXP(75/months):IF r1#>2 THEN r1#=2« rate#=r1#-1:r9#=rate#*100« p0#=balance#+payment#*months-principal#« p9#=(balance#+payment#*(r1#^months-1)/rate#)/r1#^months-principal#« IF p0#<0 OR p9#>0 THEN« r2#=0« ELSE« flop%=0« WHILE ABS(r9#-r0#)>.001« flop%=1-flop%« IF flop%>0 THEN« r2#=(r0#+r9#)/2« ELSE« r2#=r0#-p0#*(r9#-r0#)/(p9#-p0#)« END IF« r1#=(1+r2#/100/peryear(site))^(1/compound(site))« rate#=r1#-1« p2#=(balance#+payment#*(r1#^months-1)/rate#)/r1#^months-principal#« IF p2#>0 THEN« r0#=r2#:p0#=p2#« ELSE« r9#=r2#:p9#=p2#« END IF« WEND« END IF« value#(2)=r2#« RETURN« « fper:« value#(3)=LOG((payment#-rate#*balance#)/(payment#-rate#*principal#))/LOG(r1#)/12#« RETURN« « fpay:« value#(4)=rate#*(principal#*r1#^months-balance#)/(r1#^months-1)« RETURN« « fbal:« value#(5)=principal#*rl#^months-payment#*(r1#^months-1)/rate#« RETURN«
The only one of the above routines that's lengthy is fintr. There's no simple formula for the interest rate, so we must zero in on the correct value by repeated calculations.
Now to display the calculated values:
showval:« FOR j=1 TO 5« LOCATE j,1« IF j=cal THEN« PRINT "*";« ELSE« PRINT " ";« END IF« PRINT title$(j);SPACE$(20)« LOCATE j,12« PRINT USING pudef$(j);value#(j)« NEXT j« RETURN«
For a good human interface, I wanted to distinguish between the calculated item and the entered values. The title for the value being calculated will be preceded by an asterisk. SPACE$ is used to generate a string of blanks to wipe out any old values.
A Choice Is Made
menuhit:« ms=0« IF mn>4 THEN« mn1=MENU(1)« ON mn-4 GOSUB newcalc, style« END IF« RETURN«
Here's the routine to handle menu selections. The value mn, given the value of MENU(0) in the calling routine, is used to determine which menu is involved. MENU(1) tells us which item from the menu has been selected. We then subtract 4 from mn to get an offset of 1 or 2 for the ON-GOSUB statement.
newcalc:« IF mn1<6 THEN« MENU 5,cal,1« cal=mn1« MENU 5,cal,2« ELSE« IF mn1=6 THEN kwit=1« END IF« RETURN« « style:« IF mn1<3 THEN« MENU 6,site,1« site=mn1« MENU 6,site,2« END IF« RETURN«
The newcalc subroutine is called when menu 5, the Calculate menu, is selected. If the item selected from that menu is 1-5, the previously selected menu item has its checkmark removed, and a checkmark is placed beside the newly selected item. The value of cal is updated to show which variable is now being calculated. If menu item 6, Quit, was chosen, we instead set the value of kwit accordingly. The style subroutine sets site to the selected country when an item is selected from menu 6, the Country menu.
eek:« x=MOUSE(3):y=MOUSE(4)« IF x>5 AND x<190 THEN« v=INT((y+8)/8)« IF v>0 AND v<6 AND v<>cal THEN« LOCATE v,12:PRINT SPACE$(20)« LOCATE v,12:INPUT value#(v)« LOCATE v,12:PRINT USING pudef$(v);value#(v)« END IF« END IF« RETURN«
When the left mouse button is clicked, the eek subroutine allows entry of a new value. It's important to read MOUSE(0) before reading the mouse's position, but in this case, that's already been done in the event routine that calls eek. The x and y coordinates of the mouse pointer's current position come from MOUSE(3) and MOUSE(4), since those functions return the position of the mouse when the button was clicked. MOUSE(1) and MOUSE(2) return the mouse's position at the time of the MOUSE(0) call, so either would probably give comparable results in this case. Remember that we are reading pixel positions, not character positions. Before recognizing a click as a request to enter input, we check that the pointer was reasonably close to one of the displayed values. One more limitation is that we won't allow an entry for the cal variable: The computer calculates that value.
Once we know it's a valid variable, we clear the old value using SPACE$, input a new value, and then print it neatly formatted in the space provided.
Let's give the program a trial run. You'll see the window appear. If you have used the initial values suggested, you'll notice that the program has calculated a payment of $131.04. That's the Canadian computation. Now press the right button, slide the mouse pointer up to the Country menu, and move down to USA before you release the button. The payment should change to $132.16.
This is a ten-year mortgage. Let's see what the balance would be after five years. Use the right button (also called the menu button, for obvious reasons) to select the Balance option from the Calculate menu. The balance will show a slightly negative amount. That's okay (each payment is rounded up a fraction of a penny, so the final payment will be slightly less than zero). Next, move the pointer up to the Years value in the display window menu and click the left button. The computer is inviting you to enter a new value: Enter 5 for five years. Observe that the balance still due after five years is a little over $6,000.00.
How long to pay it off at $150 a month? Select Years from the Calculate menu. Change the Balance value to 0 and the Payment value to 150. The answer is a little over eight years. If you change the interest rate to 12 percent, you'll see that it would take over nine years to pay off the loan. At 18 percent, you wouldn't live long enough to pay it off at $150 a month, and at 20 percent, it's impossible (note the Years value is set to zero to indicate the error). When you've snooped through the combinations enough to satisfy yourself, select Quit. And don't forget to save the program. If your answers don't match these, check the formulae for typographical errors.
After running through this exercise, think how different things would be on any eight-bit computer. It's not just the mortgage calculation; it's the style of the machine. With a fresh approach, you can make your Amiga more flexible and useful than any computer you've used before.