LZ/LZ64 Programming Manual Chapter 3-8
3 Loops and branches
So far we have only considered programs which run in a straight line from start to finish. They consist of a number of instructions which are executed in the order they appear in the program; if you want to carry out an instruction more than once you must repeat it.
That is clearly very inefficient. A far more efficient method is for the program to be able to loop around a particular part as many times as you require, or until a certain condition is met.
There are a number of ways of doing this in OPL.
The first two are the DO/UNTIL and the WHILE/ENDWH loops. These are known as structures. They operate in a similar way to each other, with one difference.
You can have up to eight loops nested within each other.
The DO/UNTIL loop
Here is an example of a DO/UNTIL loop:
First 10 is assigned to a%. The loop starts on the next line, with the instruction DO. This says to OPL:
"Execute all the following instructions until an UNTIL is reached. If the condition following UNTIL is not met, repeat the same set of instructions until it is."
The next line displays "A=", followed by the value of a%. The first time through the loop, this is 10.
Next, the value of a% has 1 subtracted from it so that a% is 9. Now comes UNTIL, followed by a condition. The condition is that a% is equal to zero. It isn't yet, so the program returns to DO and the loop is repeated. Now a% decrements to 8, and again the condition fails. This process continues until a% does equal zero.
When a% equals zero the loop finishes and the program continues with the instructions after UNTIL.
The WHILE/ENDWH loop
The WHILE/ENDWH loop is similar, except that the test condition is at
the beginning. For example:
PRINT "a is greater than b"
BREAK can be used in conjunction with an IF statement, see below, to break out of a DO or WHILE loop. It jumps to the instruction after the end of the loop.
Labels and jumps
Another command which can direct the program out of a straight sequence is GOTO. This jumps to a label.
In this example, when the program reaches the GOTO, it jumps to the
line beginning with the label exit::.
PRINT "MISS THIS LINE"
PRINT "AND THIS ONE"
Labels end in a double colon.
The label must be in the same procedure as the GOTO, and the jump is not conditional, it always happens.
GOTO is a way of branching, but it is a fairly crude tool and can make procedures difficult to read.
Better is the IF/ELSEIF/ELSE/ENDIF structure. This structure is used to perform one or more instructions IF a condition is met. If the condition is not met, you can use an ELSEIF instruction, to test for another condition. You can have any number of ELSEIF instructions within an IF/ENDIF structure.
After all likely things are catered for by the ELSEIF instructions, other
possibilities can be catered for by an ELSE statement, followed at the end
by the ENDIF statement. Here is an example:
PRINT "THAT KEY IS"
IF g%>64 AND g%<91
PRINT "UPPER CASE"
ELSEIF g%>96 AND g%<123
PRINT "lower case"
PRINT "NOT A LETTER."
This just waits for a key to be pressed, and then prints that it is either a lower or upper case letter. (If you don't realise the significance of the numbers 64 and 91, see Appendix A.) If it is not a letter at all, then that is printed, as allowed for by the ELSE statement.
ELSEIF and ELSE statements are optional, but for every IF there must be a corresponding ENDIF.
ESCAPE OFF, GET, KEY and INPUT must be used with caution in loops for the following reasons:
To halt the execution of a procedure, you normally press ON/CLEAR then Q:
|**||raise to a power|
|-||unary minus (make negative)|
|>=||greater than or equal to|
|<=||less than or equal to|
|<>||not equal to|
|- (Unary minus) NOT|
|= > < <> >= <=|
The percent operator % is rather different as its effect depends on the operator it is combined with. The way % works is described in the calculator chapter of the operating manual.
An expression such as a+b+c presents no problems, as the result is
the same whichever addition is done first. However, you may want
to enforce precedence with brackets. For example:
is evaluated in the order: b multiplied by c, then divided by d, then
added to a. To perform the addition and the division before the
multiplication, use brackets:
b% will first be raised to the power of c% and the resulting value will be used as the power of a%.
When in doubt, simply use brackets.
Precedence of integer and floating point values
You are free to mix floating point and integer values, in expressions, but be aware how OPL handles the mix:
For example, your procedure might include the expression:
This is handled like this: b% is converted to floating point and added to c. The resulting floating point value is then automatically converted to an integer in order to be assigned to the integer variable a%.
These conversions may produce odd results, so be careful. For example, a%=3.0*(7/2) assigns 9 to a%, but a%=(3.0*7)/2 assigns 10 to a%.
The comparison operators and logical operators are based on the idea that
a certain situation can be evaluated as either true or false. For example,
if a%=6 and b%=8, a%>b% would be false. They are useful for setting up
alternative paths in your procedures. For example you could say:
You can also make use of the fact that the result of these logical expressions is represented by an integer:
These integers can be returned to a variable or printed out to the screen to tell you whether a particular condition is true or false, or used in an IF statement.
For example, in a procedure you might arrive at two sub-totals, a and b. You want to find out which is the greater. So include the statement, PRINT a>b. If zero is displayed, a and b are equal or b is the larger number but if - 1 is displayed, 'a>b' is true - a is the larger.
|<||a<b||True if a less than b||-1|
|False if a greater than or equal to b||0|
|>||a>b||True if a greater than b||-1|
|False if a less than or equal to b||0|
|<=||a<=b||True if a less than or equal b||-1|
|False if a greater than b||0|
|>=||a>=b||True if a greater than or equal to b||-1|
|False if a less than b||0|
|<>||a<>b||True if a not equal to b||-1|
|False if a equal to b||0|
|=||a=b||True if a equal to b||-1|
|False if a not equal to b||0|
You can use these-operators with a mixture of floating point or integer values. However, if one side of the comparison is floating point, and the other is an integer, the integer is converted to a floating point. So if a%=1 and b=1.2, b>a% is true. You can't use a mix of string and numeric values, so a$<b is invalid.
Few programmers need the following information, so skip the rest of the chapter if it seems daunting.
The logical operators AND, OR and NOT have different effects depending on whether they are used with floating point numbers or integers:
1 When used with floating point numbers only, the logical operators have the following effects:
|a AND b||True if both a and b are non-zero||-1|
|False if either a or b are zero||0|
|a OR b||True if either a or b are non-zero||-1|
|False if both a and b are zero||0|
|NOT a||True if a is zero||-1|
|False if a is non-zero||0|
2 When used with integer values only, AND, OR and NOT are bitwise logical operators.
The way the Organiser holds integer numbers internally is as a 16-bit binary code. So, 7 looks like this: 0000000000000111 The Organiser's numerical range is +32767 to -32768. 32767 is the largest number that can be represented with 15 binary bits. The 16th bit is used for the + or -.
As the operators are bitwise they perform the operation on first the 1st bit, then the 2nd, up to the 16th bit.
AND The statement PRINT 12 AND 10 prints 8. To understand this, write 12 and 10 in binary:
12 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 10 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0
AND acts on each pair of bits. Thus, working from left to right -
discounting the first 12 bits:
1 AND 1 --> 1
1 AND 0 --> 0
0 AND 1 --> 0
0 AND 0 --> 0
The result is therefore the binary number 1000, or 8.
OR What result would the statement PRINT 12 OR 10 give? Again, write
down the numbers in binary and apply the operator to each pair of digits:
1 OR 1 --> 1
1 OR 0 --> 1
0 OR 1 --> 1
0 OR 0 --> 0
The result is the binary number 1110, or 14 in decimal.
NOT NOT works on only one number. It returns the one's complement, i.e. it replaces 0s with 1s and 1s with 0s.
So if 7 looks like this: 0000000000000111
NOT 7 will look like this: 1111111111111000
This is the binary representation of -8.
Hint. A quick way of calculating NOT for integers is to add 1 to the original number and reverse its sign. Thus, NOT 23 is -24, NOT 0 is -1 and NOT -1 is 0. The last two results are the same for floating points.
5 Handling data files
When you use the SAVE option from the main menu your records are saved in a file called MAIN. You can also create extra data files in the Xfiles option. You can access these files and the records in them from OPL. You can also create new files and then manipulate them. This chapter explains how to do this.
Record Dr John Wood 0982 23456 44 Broughton Rd Broughton
There are also three examples of data file handling programs at the end of the example procedures chapter. It may be a good idea to refer to those programs while you read this chapter.
Files, records and fields
The data file MAIN and any extra ones you create contain records which are divided into fields. When you save a record from the main menu with the SAVE option, you start a new field every time you start a new line by pressing DOWN.
In a name and address file, in each record, there might be a name field, a telephone number field, and separate fields for each line of the address.
Records Dr John Wood 0982 23456 44 Broughton Rd Broughton Dr John Wood --> Field 1 Fields 098223456 --> Field 2 44 Broughton Rd --> Field 3 Broughton --> Field 4
Creating a data file
Before you can start to enter data into a new data file, you must create the file on one of the devices using New in Xfiles or the CREATE command in OPL.
With CREATE in OPL you have more control, as you specify how many fields
there can be in each record, and what type of data can go in each field.
The CREATE command has this syntax:
"<dev>fname", is the device (A:, B: or C:) which the file is to be on, and then the file name. This all goes in quotes as a string, e.g. "a:clients". You can also assign this device and file name string to a string variable (e.g. cl$="a:clients") and then use the variable name (cl$) as the parameter. The file name may be up to 8 characters long.
logname is the logical file name. This may be A, B, C or D. You use this logical file name to refer to the file from within the program.
fldnm1, fldnm2 are the field names. There may be up to 16 fields in any record, and these may be given a qualifier, either % or $, to signify integer data or string data respectively. Fields containing floating point data need no qualifier. Field names may be up to 8 characters long including any qualifier used.
An example of a CREATE command might be:
When a file has been created, it is automatically open. This means records can be saved to it immediately. It also becomes the current file which means that when you use one of the commands for manipulating records, they operate on this file.
Adding records to a file
You add records to a data file field by field. First you assign some values to the current field names, then you use the APPEND command to add them to the file.
Assigning values to field names
The field names act in a similar way to variables, and can be assigned values
and used in INPUT statements. The field name must be used with the logical
file name like this:
where B is the logical file name and name$ is the name of the field. These are separated by a full stop.
Appending the fields
When you have assigned values to the fields, you add them to the open file with the APPEND command. They are always added as the last record in the data file. If the file is a new one, this will be the first record.
The APPEND command has no parameters - the field values are automatically added to the file in the correct order and format.
If you try to assign a text string to a numeric field name, an error will be reported.
See the second procedure (insert:) in program no. 6 of Chapter 8 for an example of adding records to a data file.
At any time while a data file is open, the field names currently in use can be used like any other variable - for example, in a PRINT statement, or a string or numeric expression. However, in order to operate on a particular field, you must make the record containing it the current one.
Changing the current record
Before you can erase a record or operate on the fields in it you must make it current. You can change the current record by using any of the six commands and functions below.
FIRST moves to the first record in a file.
NEXT moves to the following record in a file. If the end of the file is passed, NEXT does not report an error. The current record will then be null. This condition can be tested for with the EOF function.
BACK moves to the previous record in the file. If the current record is the first record in the file then the current record does not change.
LAST moves to the last record in the file.
POSITION moves to a particular record. For example, the instruction POSITION 3 makes record 3 (the first record is record 1) the current record. You can find the current record number by using the POS function which returns the number of the current record.
FIND moves to the record which contains a search string you specify.
Finding a record
The FIND function acts like the main menu Find, but without wild cards. The difference is that whereas the main menu Find prints the record on the screen, this function makes it the current record so that you can operate on it: editing it, erasing it, or just displaying it.
For example, the line
would make the first record. containing the string "HOLMES" the current record and return the number of that record to the variable r%. If the number returned is zero, the string was not found.
There is another function, FINDW, which is the same as FIND but does allow
wild cards. So, to find the first record containing ORDER and 89 use this
The procedure called search: in program no. 6 in Chapter 8 is an example of how to find and edit records.
Erasing a record
To erase a record, make that record current by use of one of the commands FIRST, NEXT, BACK, LAST, POSITION or FIND and then use the ERASE command. This removes the current record from the file and renumbers the ones that follow.
The final procedure (erase:) in program no. 6 in Chapter 8 is an example of how to find and erase records.
Opening a file
When you first CREATE a data file it is automatically open but it closes again when the program ends. To open the file again in another program, you use the OPEN command.
The syntax of the OPEN command is the same as for the CREATE command. You
must use the same device and file name as when you first created it, but
when opening it in a different program, you can give it a different logical
file name and give the fields different names. For example, a file which was
created in one program with the command:
can be opened in another program by the command:
Up to four files may be open at any one time, and these may be spread over any of the three devices. Each must be referred to in the program by a different logical name. So, if you have 4 files open. one is A one is B one is C and the other is D.
If you are going to edit or erase records in the file. you need to include
all the fields you are going to operate on in the OPEN command. However, if
you are just going to search for strings and display records. you only need
to include the first field in the OPEN command, like this:
Changing the current file
When a file is created or opened, that file is then automatically the
current file and all access is to that file until you say you want to USE a
different one. The USE command makes a file current. You refer to the
file by its logical file name. For example:
In this example the file with the logical file name B (as specified in the OPEN or CREATE command which opened it) becomes the current file. All access is now to this file until you change the current file with another USE command or OPEN or CREATE another file. If you attempt to use a file which has not previously been opened, an error is reported.
Closing and deleting a file
Data files close automatically when programs end. However, when you have finished accessing a particular file, you can close it with the CLOSE command which closes the current file.
You might want to close a file if you already have 4 files open in a program, and you want to open another, or if you want to delete the file. There is a command DELETE for deleting data files, but the file to be deleted must be closed first.
More information on data file handling
See the beginning of Chapter 9 for a summary of all the data file handling commands and functions. Then look up each one for more detail.
Look at programs 6, 7 and 8 in Chapter 8 for examples of data file handling programs.
6 Handling any type of file
Most of the file handling commands and functions are only for data files.
However there are three, COPYW, DELETEW and DIRW$, which operate on any type of file. You indicate the type of file with these extensions:
|Data file and diary file
(ODB stands for Organiser Data Base.)
|Comms Link setup file||.COM|
|Pager setup file||.PAG|
|Diary file from XP or CM||.DIA|
There are also two extra extensions for COPYW only. Use these to copy only one part of an OPL procedure.
|OPL procedure (text only)||.OPT|
|OPL procedure (object only)||.OPO|
You can also use wild cards with COPYW, DELETEW and DIRW$. The wild cards are the normal ones: + is any character, * is any group of characters.
Copies all the OPL procedures whose names end in 88 from A: to B:
Deletes an the notepad files on C:
DIRW$ ("A: R*")
Returns the name of the first file of any type on A: which starts with the letter R.
Diary files are saved as data files. Each entry is a record with this two field format:
in this example, the first field carries this information:
|1200||The start time|
|01||The duration - one 15 minute interval|
|00||The alarm pre-warning time. In this case there is no alarm so it's zero.|
You can open a saved diary file in Xfiles, and then find and update records in it. For example, you could find all your annual entries such as birthdays and change 1989 to 1990. If you then restored the diary and merged it with the current one you wouldn't need to put in all the birthday entries again at the end of the year. However, be careful to use the right format, or you won't be able to restore the diary.
You could also do this in OPL using the data file handling and string handling commands. There is a program in Chapter 8 which does this.
7 Error handling
This chapter covers commonly made errors, then error trapping. There is a list of the OPL error messages in Appendix D.
All programming languages are very particular about the way commands and functions are used, especially in the way program statements are laid out.
Below are a number of errors which are easy to make in OPL. The incorrect statements are in bold and the correct versions are on the right.
Omitting the colon between statements on a multi-statement line:
|a$="text" PRINT a$||a$="text" :PRINT a$|
Omitting the colon after a called procedure name:
Omitting one or more of the colons after a label:
Omitting the space before the colon between statements on a multi-statement line:
a$=b$: PRINT a$
Parameter errors Passing a floating point value to a procedure which requires an integer - here the procedure proc2:(x%).
This may also happen when a procedure is called from the calculator. The calculator converts all numbers to floating point, so:
Passing an integer to a procedure which requires a floating point value - here the procedure proc3:(x)
Passing the wrong number of parameters to a procedure - here, the procedure proc4:(x,y)
Integer size error
OPL only allows numbers between minus 32768 and plus 32767 to be assigned to integer variables, so any expression which exceeds these limits will cause an error:
The structures allowed within OPL are DO/UNTIL, WHILE/ENDWH and IF/ELSEIF/ELSE/ENDIF. These may all be nested within one another to up to eight structures deep. Attempting to nest to a greater depth than this will cause an error. Mixing up the three structures e.g. by matching up DO with WHILE, will also cause an error:
If an error occurs when you run a program, the program stops and an error message is displayed.
If you are running the procedure from the main menu, just press SPACE to return to the menu.
However if you are running it from the Prog menu, you get a chance to edit it. Here a call has been made to a procedure called subproc: which does not exist:
Edit A:MAINPROC Y/N
Press Y if you want to edit the procedure. If the source code is available, you are returned to the OPL editor to correct the offending line. Press N or ON/CLEAR if you don't want to edit it.
When you have done this, press MODE to get the editor menu, and either translate, save or quit the procedure. If you quit, the edits you have made this session are abandoned.
In the case of the run time errors described above, the program stops to display the error message. There are ways of avoiding this by trapping errors and dealing with them yourself within the program - but they put you in full control and must be used carefully. The tools used to control errors are ONERR, TRAP, ERR, ERR$ and RAISE:
ONERR label:: and ONERR OFF
ONERR, is used to redirect program control to a label if an error occurs. This is useful if you want to provide your own error handling routine, such as printing out a message for an error you anticipate. ONERR is followed by the label name which ends in two colons. The label itself can be either in the same procedure or in a procedure before it in the same program.
In the example below, LPRINT is being used to print to an attached
printer. Normally, when no printer is connected, the message
DEVICE MISSING is displayed. Here a more precise message,
CONNECT PRINTER, is supplied by the programmer:
LPRINT "Dear Sir"
ONERR OFF :IF ERR=194 :RAISE ERR :ENDIF
PRINT "CONNECT PRINTER"
The first line causes a jump to the label noprint:: if an error occurs. If no printer is connected, the LPRINT command causes such a jump and the message "CONNECT PRINTER" is displayed. If there is one connected, "Dear Sir" is printed and the lines after the label are never run, because of the RETURN before it.
Notice that the first instruction after the label noprint:: is ONERR
OFF. This is very important because if you don't do this after the
ONERR label:: command is used, all subsequent program errors -
even in other procedures called - result in the program being directed
to the same label. This diagram illustrates how two completely
different errors cause a jump to the same label, and cause the same
explanatory message to be printed out:
PRINT "Explanation of log error"
Risks of ONERR label::
As all errors go back to the same label unless you switch ONERR OFF, it is very easy to create an endless loop by mistake. If this happens, you cannot press ON/CLEAR and Q to break out, as this just makes control go to the label as any other error would - so, you have to take the battery out of the Organiser, and lose everything in the internal memory. To avoid this:
TRAP traps errors on a specified command only, so it doesn't need to be cancelled like ONERR does. It can be used with any of the commands listed below:
APPEND BACK CLOSE COPY COPYW CREATE DELETE DELETEW ERASE EDIT FIRST INPUT LAST NEXT OPEN POSITION RENAME UPDATE USE
The trap command immediately precedes any of these commands, separated from
it by a space - e.g:
TRAP INPUT a%
When INPUT is used without TRAP and a text string is entered
when a number is required, the display just scrolls up and a question
mark is shown, prompting for another - valid - entry. When you put
TRAP in front of INPUT, the command is executed in the usual way,
but if an error occurs the next line of the program is executed as if
there had been no error. The next line in the example below is a
PRINT "ENTER AGE",
TRAP INPUT a%
PRINT "NUMBER NOT WORD"
The example above also uses the ERR function.
The ERR function
When errors occur in a program, the number of the error is accessible by using the ERR function. This means that you can be absolutely sure which error you are dealing with.
The anticipated error in the lines below is 246 (NO PACK). If 246
occurs when trying to open the MAIN file on pack B: a helpful
error message is printed out. However, just in case a different error
occurs, the next lines make sure that the standard error message for
that error is printed.
TRAP OPEN "B:MAIN",A,a$
PRINT "NO PACK IN B:"
TRAP OPEN "B:MAIN",A,a$
The OPL error messages are listed in Appendix D.
If you are using commands to trap errors and handle them yourself, then at some during the development stage of your program you need to test your error handling routines. An easy way to do this is with the RAISE command. You can generate an error that you think might occur when the program is in use, and see if the error handling routine takes care of it in the way you anticipate. For example, this statement causes the NO PACK error to be generated:
8 Example programs
This chapter contains example programs written in OPL. The programs are not intended to demonstrate all the features of OPL, but they should give you a few hints. To find out more about a particular command or function, refer to Chapter 9. Each of the procedures must be entered separately, you can't enter two procedures in one block. Chapter 1 explains how to type in, translate, save and run a procedure.
In the listings here, variables are shown in lowercase and commands and functions in uppercase. However, it doesn't matter which you use when you enter procedures into the Organiser:
Be careful to type in the necessary spaces:
Lines beginning with the command REM are remarks; they are there to explain things. They do not affect the way a procedure runs and you don't have to type them in if you don't want to.
1 Days (Version 1)
This procedure works out the number of days you've been alive. Substitute your own date of birth for the one supplied here.
days1: LOCAL birth%,now%,answer% birth%=DAYS(14,6,1957) now%=DAYS(DAY,MONTH,YEAR) answer%=now%-birth% PRINT answer% :GET
birth% is your date of birth.
now% is the current date.
answer% is the current date minus your date of birth.
All the OPL date functions return values accessed from the Organiser clock and calendar. The function DAYS returns the number of days since the beginning of the calendar on a particular date.
2 Days (Version 2)
This procedure is a more flexible one which works out the number of days between boy two dates that you input. When you run the procedure you are prompted to enter the day, month and year of the first date then the second date.
days2: LOCAL d1%, m1%, y1%, d2%, m2% y2% PRINT "ENTER FIRST DAY" INPUT d1% PRINT "ENTER FIRST MONTH" INPUT m1% PRINT "ENTER FIRST YEAR" INPUT y1% PRINT "ENTER SECOND DAY" INPUT d2% PRINT "ENTER SECOND MONTH" INPUT m2% PRINT "ENTER SECOND YEAR" INPUT y2% PRINT DAYS(d2%,m2%,y2%)-DAYS(d1%,m1%,y1%) GET
d1%, m1% and y1% are the day month and year of the first date.
d2%, m2% and y2% are the day month and year of the second date.
This procedure turns the Organiser into a dice. When the program is run, a message is displayed saying that the dice is rolling. You then press S to stop it. A random number from one to six is displayed and you choose whether to roll again or not.
dice: LOCAL dice%,key% KSTAT 1 top:: CLS :PRINT "****DICE ROLLING****" PRINT "PRESS S TO STOP" DO dice%=(RND*6+1) UNTIL KEY$="S" CLS PRINT "********* ";dice%;" ********" BEEP 50,100 AT 1,4 :PRINT "ROLL AGAIN Y/N" label:: key%=GET IF key%=%Y GOTO top:: ELSEIF key%=%N RETURN ELSE GOTO label:: ENDIF
dice% is a random number from 1 to 6.
key% is the ASCII value of the keypress read by the GET function.
This is how dice: displays 1, 2, 3, 4, 5 or 6 randomly. The RND function returns a random floating point number, between 0 and 1 (not including 1). It is then multiplied by 6 and 1 is added (so that you get numbers from 1 to 6 instead of from 0 to 5). It is rounded down to a whole number by assigning to the integer dice%.
Identifying keypresses of Y and N
The ASCII value of the character on the key you press is returned by the GET function and assigned to key%. In OPL you get the ASCII value of any character by putting % in front of it, so %Y is the ASCII value of Y and %N is the ASCII value of N. In case you had the keyboard set to lowercase, KSTAT 1 is used at the start of the procedure to set it to uppercase, so that you are not pressing y and n.
4 Mortgage calculator
This program calculates monthly mortgage payments. When you run it, you have to enter the amount of the loan, the interest rate and the term in years. Then you specify the source of the loan by selecting from a menu. The program does not allow for tax relief.
The program is made up to two procedures - mortgage: and q: - a general input routine. You must type each one in separately. The input routine could be called by any procedure which needs to prompt the user to enter a floating point number.
mortgage: LOCAL num%,loan,x,term,rate,pay,ques$(2) CLS PRINT "EVALUATE MONTHLY" PRINT "MORTGAGE PAYMENT" PAUSE 30 ques$=CHR$(63)+" " REM CHR$(63) is a "?" - See Appendix A loan=q:("ENTER LOAN"+ques$) DO rate=q:("INTEREST RATE % "+ques$) UNTIL rate>0 AND rate<99 DO term=q:("ENTER TERM (YRS)"+ques$) UNTIL term>.5 AND term<100 num%=MENU("BUILDING-SOCIETY,BANK,OTHER") IF num%=0 :RETURN :ENDIF rate=rate/100 :x=1+11*(num%/2) pay=loan*rate/12/(1-((1+rate/x)**(-x*term))) CLS :PRINT "MONTHLY PAYMENT" PRINT FIX$(pay, 2, -8) GET RETURN q:(a$) LOCAL z CLS :PRINT a$,CHR$(16); INPUT z CLS RETURN(z)
loan, term, rate and pay are the amounts of the loan, the term in years, the interest rate and the monthly payments.
ques$ is "? ".
num% is the number of the menu item you choose.
x is a variable used in the calculation at the end.
z is the variable used for the values you input when q: is running.
The main procedure mortgage: calls q: three times, passing it a string to be printed out as a prompt. The text string is passed to q: as a parameter. The values returned are assigned to the variables loan, rate, and term.
How "?" is printed
The CHR$ function converts the ASCII value 63 to the question mark character.
How the input routine beeps
The CHR$ function converts the value 16 to the beep control character. This is then "printed" out.
5 Chase Game
The next two procedures make up a game which demonstrates the use of user defined graphics (UDG's). Each procedure must be typed in separately.
The object of the game is to avoid being caught by the pursuers. Your movable man can jump up and down: press the X key to jump down and the S key to jump up. At the end of the game your score will be displayed on the screen.
To pause the game, press ON/CLEAR and to restart press it again. To quit out of the game press ON/CLEAR then Q.
game: LOCAL e$ (2) LOCAL a%,b%,b1%,c%,c1%,x%,y%,i%,sc% graphic: :CURSOR OFF e$=CHR$(4)+CHR$(6) b%=20 :c%=12 :x%=3 :y%=1 DO CLS :PRINT REPT$(CHR$(158),80) AT x%,y% :PRINT CHR$(7) a%=1 :c%=1+RND*4 DO :c1%=1+RND*4 :UNTIL c%<>c1% DO AT a%,c% :PRINT CHR$(0) AT a%,c1% :PRINT CHR$(0) :BEEP b%,10*b% AT a%,c% :PRINT CHR$(1) AT a%,c1% :PRINT CHR$(1) :BEEP b%,10*b% AT a%,c% :PRINT CHR$(2) AT a%,c1% :PRINT CHR$(2) :BEEP b%,10*b% AT a%,c% :PRINT CHR$(3) AT a%,c1% :PRINT CHR$(3) :BEEP b%,10*b% AT a%,c% :PRINT e$ AT a%,c1% :PRINT e$ :BEEP b%,10*b% AT a%,c% :PRINT CHR$(5) AT a%,c1% :PRINT CHR$(5) :BEEP b%,10*b% AT a%,c% :PRINT " " :AT a%,c1% :PRINT " " i%=KEY IF i% IF i%=%S AND y%>1 AT x%,y% :PRINT CHR$(158) y%=y%-1 :AT x%,y% :PRINT CHR$(7) ENDIF IF i%=%X AND y%<4 AT x%,y% :PRINT CHR$(158) y%=y%+1 :AT x%,y% :PRINT CHR$(7) ENDIF ENDIF a%=a%+1 IF a%=x% AND (c%=y% OR c1%=y%) :REM Hit i%=0 DO AT x%,y% :PRINT CHR$(170+i%) BEEP 10,100+i% i%=i%+1 BEEP 10,100-i% UNTIL i%=30 b%=b%+5 :a%=20 :x%=x%+2 IF x%>20 CLS :PRINT "GAME OVER" PRINT "SCORE:",sc% :PAUSE 40 WHILE KEY :ENDWH :REM Drain buffer GET :RETURN ENDIF ENDIF UNTIL a%=20 sc%=sc%+1 IF b%>12 b%=b%-2 ELSEIF b%<6 IF b1% b%=b%-1 :b1%=0 ELSE b1%=1 ENDIF ELSE b%=b%-1 ENDIF UNTIL 0 graphic: UDG 0,0,0,28,30,30,30,28,0 UDG 1,0,0,14,31,30,31,14,0 UDG 2,0,0,7,14,12,14,7,0 UDG 3,0,0,3,7,6,7,3,0 UDG 4,0,0,1,3,3,1,0,0 UDG 5,0,0,0,1,1,1,0,0 UDG 6,0,0,24,16,4,16,24,0 UDG 7,30,14,4,14,30,14,11,25
User defined graphics
The main procedure, game:, calls graphic: which then uses the UDG command 8 times to set up the graphics characters used in the game. The last UDG command is the one which sets up the little man. User defined characters are explained in Appendix A.
6 Data file handling procedures
The main procedure below creates a data file called addr on device A, to contain names, addresses, post codes and telephone numbers. It is followed by 4 other procedures which allow you to insert. search for, alter and erase records in the file. When you run files:, a menu giving you a choice of these options is displayed.
Each of these 5 procedures must be typed in separately.
files: LOCAL m% IF NOT EXIST("A:addr") CREATE "A:ADDR",A,n$,ad1$,ad2$,ad3$,pc$,tel$ ELSE OPEN " A:ADDR",A,n$,ad1$,ad2$,ad3$,pc$,tel$ ENDIF DO m%=MENU("INSERT,SEARCH,ALTER,ERASE,QUIT") IF m%=0 or m%=5 :STOP ELSEIF m%=1 :insert: ELSEIF m%=2 :search: ELSEIF m%=3 :alter: ELSEIF m%=4 :erase: ENDIF UNTIL 0 :REM do loop forever insert: PRINT "ENTER NAME" :INPUT A.n$ CLS :PRINT "ENTER STREET" :INPUT A.adl$ CLS :PRINT "ENTER TOWN" :INPUT A.ad2$ CLS :PRINT "ENTER COUNTY" :INPUT A.ad3$ CLS :PRINT "ENTER PCODE" :INPUT A.pc$ CLS :PRINT "ENTER TELNUM" :INPUT A.tel$ APPEND search: LOCAL recnum%,search$(30) top:: FIRST :CLS :PRINT "FIND:"; TRAP INPUT search$ IF ERR=206 RETURN ENDIF recnum%=FIND(search$) IF recnum%=0 CLS :PRINT "NOT FOUND" :PAUSE 20 GOTO top:: ENDIF DO DISP(-1,"") :NEXT :recnum%=FIND(search$) IF recnum%=0 :CLS PRINT " NO MORE ENTRIES" PAUSE 20 :RETURN ENDIF UNTIL 0 alter: LOCAL recnum%,search$(30),k% DO FIRST :CLS PRINT "ALTER:"; :TRAP INPUT search$ IF ERR=206 :RETURN :ENDIF recnum%=FIND(search$) IF recnum%=0 CLS :PRINT "NOT FOUND" PAUSE 20 :CONTINUE ENDIF DO KSTAT 1 :CLS :AT 1,2 :PRINT "EDIT Y/N" k%=VIEW(1,A.n$) IF k%=%Y :CLS EDIT A.n$ :EDIT A.ad1$ EDIT A.ad2$ :EDIT A.ad3$ EDIT A.pc$ :EDIT A.tel$ :UPDATE :RETURN ELSEIF k%=%N :NEXT :recnum%=FIND(search$) IF recnum%=0 CLS :PRINT "NOT FOUND" :PAUSE 20 :BREAK ENDIF ENDIF UNTIL 0 UNTIL 0 erase: LOCAL recnum%,search$(30),k% FIRST :CLS :PRINT "ERASE:"; TRAP INPUT search$ IF ERR=206 :RETURN :ENDIF recnum%=FIND(search$) DO IF recnum%=0 CLS :PRINT "NOT FOUND" :PAUSE 20 :RETURN ENDIF ask:: KSTAT 1 :AT 1,2 :PRINT "ERASE Y/N" k%=VIEW(1,A.n$) IF k%<>%Y AND k%<>%N GOTO ask:: ELSEIF k%=%Y ERASE ELSEIF k%=%N NEXT :recnum%=FIND(search$) ENDIF UNTIL EOF
m% is the number of the menu item you select
recnum% is the record number returned by FIND.
search$ is the search clue you enter.
k% is the ASCII value of the key you press whilst the found record is displayed. (%Y is the ASCII value of Y, %N is the ASCII value of N.)
Creating the data file
The first procedure, files:, creates or opens a file called addr on device A: with 6 fields for each record. The six field names are n$ for the name, ad1$, ad2$, and ad$3 for each line of the address, pc$ for the post code and tel$ for the phone number. The file is given the logical name A.
Notice how in insert: the 6 fields of the record are input one by one. The field names are used like variables and preceded by the logical file name (A) and a full stop. Then the APPEND command is used; this is necessary to actually add the record to the end of the file.
Displaying the current record
When a record containing a particular string has been found by FIND it becomes the current record. DISP with -1 as the first parameter, displays it.
In the procedure alter:, VIEW is used to display just the first field of the record, while you decide whether to edit it. If you choose to, each field is then displayed by the EDIT function, which allows you to alter what is on the screen.
7 Telephone logging (data file handling)
These 3 procedures make up a program which allows you to log telephone calls. It stores their duration with your comments in a data file. It could easily be adapted to record the duration of any other activity.
The first procedure displays a menu like this with a phone UDG and the clock. Because the clock uses UDGs 3, 4, 5, 6, 7 and 1, UDGs 0 and 2 been used for the phone and the underline:
logger: LOCAL m% UDG 0,31,21,14,10,31,31,0,31 UDG 2,0,0,0,0,0,0,0,31 PRINT CHR$(0);REPT$(CHR$(2),14) CLOCK(1) m%=MENUN(2,"Logcall,Viewcalls") IF m%=0 RETURN ELSEIF m%=1 logcall: ELSE m%=2 viewcall: ENDIF logcall: LOCAL k%,h%,m%,s%,sec%,start$(8),swof% ESCAPE OFF IF NOT EXIST("A:LOG") CREATE "A:LOG",B,date$,t$,comment$ ELSE OPEN "A:LOG",B,date$,t$,comment$ ENDIF swof%=PEEKB($7C) POKEB $007C,0 :REM no auto turn off B.t$="00:00:00" B.date$=DATIM$ start$=RIGHT$(DATIM$,8) h%=0 :m%=0 :s%=0 :sec%=SECOND DO IF sec%<>SECOND BEEP 1,100 sec%=SECOND :s%=s%+1 IF s%=60 :s%=0 :m%=m%+1 IF m%=60 :m%=0 :h%=h%+1 ENDIF ENDIF B.t$=REPT$("0",-(h%<10))+NUM$(h%,2)+":" B.t$=B.t$+REPT$("0",-(M%<10))+NUM$(m%,2)+ B.t$=B.t$+REPT$("0",-(s%<10))+NUM$(s%,2) ENDIF AT 1,1 :PRINT "Started:";start$ PRINT "Time:";B.t$ PAUSE -1 :REM save battery k%=KEY IF k%=1 GOTO exit:: ENDIF UNTIL k%=13 PRINT "Comments "+CHR$(63) TRAP INPUT B.comment$ IF ERR=206 GOTO exit:: ENDIF CLS :PRINT "Saving" :APPEND exit:: CLOSE POKEB $007C,swof% :REM restore auto turn off ESCAPE ON viewcall: LOCAL k% TRAP OPEN "A:LOG",B,date$,t$,comment$ IF ERR :RETURN :ENDIF DO k%=DISP (-1,"") NEXT IF k%=1 RETURN ENDIF UNTIL EOF AT 1,4 :PRINT " NO MORE ENTRIES" PAUSE 25 RETURN
k% is used for the keypresses read by KEY.
sec% is the actual number of seconds past the minute at the start of the procedure.
s% is the seconds shown counting up.
m% is the minutes shown counting up.
h% is the minutes shown counting up.
start$ is the time the call started.
swof$ is the initial setting of auto-switch off.
The data file
A data file called LOG is created on device A: with three fields in each record: one for the date, one for the time which the call took, and one for your comments. When you run the program and log a call the date, time and comment are appended to the data file as a record. Select Viewcalls to look through all the appended records.
You could also open the file A LOG in Xfiles, and search for the records using Find.
How the stopwatch counts up the elapsed time
The actual number of seconds past the minute is read from the system clock by the SECOND function and assigned to sec% at the start. Then the SECOND function is used again, and if it is no longer equal to sec% - i.e. when a second has elapsed - 1 is added to s% and sec% is assigned the actual number of seconds again. Whenever s% is 60, it is reset to 0 and 1 is added to m% and so on.
Normally ON/CLEAR pauses the execution of a procedure so that Q can be pressed to quit. At the start of this procedure ESCAPE OFF is used. This means that ON/CLEAR does not pause the program, and can thus be read by the KEY and DISP functions and cause a RETURN to the PROG menu instead of an ESCAPE error.
Displaying the records
When A:LOG is opened in viewcall, the first record is made current then displayed by DISP with - 1 as the first parameter. Then NEXT is used to make each record current in turn.
8 Diary file handling procedure
If you save your diary to a file, you can use this program to copy all the birthdays from one year to the next year. It could easily be adapted to deal with other annual entries too.
A saved diary file called "olddia" is opened. All the records containing 1989 and birthday have the 1989 changed to 1990 and are put into a file called "newdia". You can then restore "newdia" into your current diary and merge the two.
Remember to substitute the name of your saved diary for "olddia" when you type the procedure in.
birthday: CREATE "a:newdia",B,date$,text$ OPEN "a:olddia",A,date$,text$ WHILE FINDW("1989*birthday") PRINT a.text$ b.date$="1990"+mid$(a.date$,5,255) b.text$=a.text$ USE B :APPEND USE A :NEXT ENDWH GET
A saved diary file is just like any other data file. However, for more information about the format of records in diary files, see Chapter 6.