Chapter 2 Tutorials BASIC is just one of many computer languages that you can buy for your Amiga computer (you could contact HiSOFT for up to date details of the range available) but BASIC is deservedly popular for its ease of use and the speed with which a beginner can learn to produce results. On the other hand there are two common criticisms of BASIC. Firstly, many people feel that its very flexibility and tolerance encourages poor programming habits compared to highly structured languages such as Pascal. Secondly, BASIC is often the slowest language you can use, which makes it unsuitable for many time-dependent applications such as games and graphics. Our new BASIC, HiSOFT BASIC 2, goes a long way towards solving both of these problems. We have taken the best qualities from Microsoft BASIC, Pascal and other languages and combined them together to produce an implementation that is capable of running traditional BASIC programs but can also be used to produce a source that is highly structured, easy to follow, and simpler to debug. It is also extremely powerful, giving you the ability to control almost every feature of your computer, hardware and software... and, above all, it is fast. Many people familiar with older versions of the language will have some re-learning to do. Bad habits and unnecessary features, such as line numbers, can now be forgotten, although you can use line numbers if you really want to. HiSOFT BASIC does not mind if you want to take things gently and continue using them for a while. To help you get to grips with BASIC and, in particular, HiSOFT BASIC, we have written a tutorial which forms the rest of this chapter; this is aimed at users who have not used BASIC very much before and starts from first principles; experienced users may find it useful as a refresher course. Throughout the tutorials you will see some text marked with sidebars, as this paragraph is marked. These passages are diversions which need not be read the first time through but contain supplementary information which you should find useful once you have become more confident with HiSOFT BASIC. BASIC Tutorial The examples used in the following tutorial are designed to illustrate the power of HiSOFT BASIC. They will therefore tend to use its structured elements as much as possible, but do remember that you can also type in many programs from books and magazines that would work with more inferior versions of BASIC. They should run, and run much faster, with hardly any alteration at all; if there are problems, HiSOFT BASIC will point you towards which lines are causing the trouble, quickly and simply. All computer programs are simply tools which allow us to control the actions and reactions of the computer itself. HiSOFT BASIC has been written to be as compatible as possible with several different versions of the language - notably AmigaBASIC that came supplied with older Amiga's and also Microsoft QuickBASIC which is one of the most highly respected implementations of the language available for the IBM PC range of computers (and compatible machines). Language elements have also been added from Borland's Turbo Basic for the PC. We aimed at this compatibility for two reasons. Firstly, anyone who has already learnt to program in one of these different versions of BASIC will be quickly able to work with the HiSOFT BASIC language. Secondly, programs that are have already been written using these versions will run with little or no alteration under HiSOFT BASIC (but of course they should run much faster!). The latter feature is particularly important to anyone who intends to run a specific program, one that was originally intended for the IBM PC for example. It will also allow anyone who wishes to learn more about programming to buy tutorial books that were aimed to be used with one of these versions of the language. However, for anyone who is new to programming it is not all good news. HiSOFT BASIC has had to conform to the language standards used by at least two different systems, and these in turn have built upon previous versions of BASIC and so on. As a result not all commands are as self explanatory or as easy to use as we, or you, might wish. One area of difficulty is that, because HiSOFT BASIC is compatible with so many other BASICs, it has a large number of Reserved Words that you should avoid using as labels or variables e.g. Loop: PRINT "Welcome to HiSOFT BASIC!" GOTO Loop If you typed this program in and tried to execute it using HiSOFT BASIC, you would find that an error will occur on line 3. This is because Loop is a reserved word in HiSOFT BASIC (you will learn how to use it later) and thus you cannot use it as a label or variable. You must watch out for this type of error since you can easily confuse HiSOFT BASIC by using its reserved words incorrectly. See Appendix C for a list of the Reserved Words. Experienced programmers, who are sure that they know what they are doing, can remove a Reserved Word so that it can be re-defined. You do this with a line like: REM $DISABLE PRINT to remove PRINT from the Reserved Word list. You can then create a new sub-program or function called PRINT. This tutorial is intended to be a complement to the Command Reference section of this manual. Unlike the latter, here keywords are grouped logically by their general function rather than alphabetically. We have tried not to duplicate information that is perfectly obvious from the Command Reference, but rather to summarise the way that certain keywords are related to each other and interact with each other. At times it has been inevitable to make forward references to keywords or programming techniques that have not yet been covered in detail, but we have tried to keep these instances to a minimum. Please consult the Command Reference chapter frequently as you work through this tutorial - you will find understanding will dawn more quickly and more easily if you supplement your knowledge in this way. After the first few programs presented in this tutorial it will be assumed that you know how to use the HiSOFT BASIC editor; the various demonstration programs will be presented without constant reminders of how to enter and run them although we will give examples of editing techniques etc. from time to time. If you need further help, you should consult Chapter 3 on using the editor. The Building Blocks of BASIC We can look on each component of the BASIC language as a building block from which we can, step by step, construct an entire program. As we shall see later, the structured aspects of HiSOFT BASIC make it particularly easy to develop very long and complex tasks as a collection of small modules, each one of which performs a small part of the whole and which can be tested and perfected in isolation. The smallest component of the BASIC programming language is known as a keyword. Each of the keywords does a certain job, although some of them must first be given some information to work on, and many of them only function when used in conjunction with certain others. For your reference, a list of all keywords used by HiSOFT BASIC is given in Appendix C. You must ensure that you do not try to use any of these words to represent variable or subroutine names or HiSOFT BASIC will become confused as to which you really mean. The function and syntax of every single keyword will be detailed in the Command Reference. This tutorial section will not attempt to cover every one of these again, but rather try to illustrate the way that some of these building blocks can be joined together into useful routines. The most important thing is that you learn to Ôthink like a programmer'. Once you begin to see how small programs work, how the individual commands fit together to produce the desired end result, you will quickly be able to pick up new keywords as and when you need them. Some Simple Commands There are many very simple tasks you can ask your computer to do which really form the Ôbread and butter' of computer programs. The simplest example to start with is unquestionably a command like the BEEP command which, no prizes here, causes the computer to attract the user's attention. We'll try it now and enter a short program. Start by locating the HBasic2 file on your working disk or hard disk (see Chapter 1), then double-click with the mouse on the HBasic2 icon to run HiSOFT BASIC. After a short while, the editor window will appear: HiSOFT BASIC loaded Now type the following, pressing the Return key at the end of each line: PRINT "Please wait for the beep ..." BEEP The screen should look like this: Your first HiSOFT BASIC program Now hold down the right mouse button and move the mouse up to the Program menu and, holding the button down, move the mouse until you have selected the Run command. Now release the button. Running your first program This will run your program which should open a window, display its message and beep (flash the screen) ... impressed? Ok, but it was simple to do. Press a key and select New from the Project menu to create a new window; we'll move straight on to a more complex (and Amiga specific) command: LINE draws a line on the screen. By looking in the Command Reference chapter you will see that this keyword can be given several parameters which control the start location and finish location of the line. We will be dealing with LINE and other graphics commands again later in the tutorial, so do not try and take it all in at this stage. Just try this simple command for comparison with BEEP. Remember that it does not matter whether you type the HiSOFT BASIC commands in upper or lower case or even a mixture of the two; LINE, line and Line are all the same to HiSOFT BASIC. The editor will upper-case HiSOFT BASIC Reserved Words automatically if you have selected Show Keywords in the Settings... requester from the Settings menu. This is useful as a quick syntax check after you have completed each line. PRINT "Just a short line ..." LINE (100, 100) - (200, 200) Type this in: Your second program, using LINE You can run this as before, by selecting Run from the Program menu or you can run it by using a keyboard shortcut, Ctrl-X - it achieves exactly the same as selecting Run but some people prefer the keyboard to the mouse. There are many keyboard shortcuts within the HiSOFT BASIC editor. The LINE program running Press a key so that you are back looking at the source code of the LINE program, then select New from the Project menu to create a new window. By this point we have several windows open, so click in the Close box, at the top left of the window, of our two earlier programs to forget them - click Lose when the requester appears. The commands that you can give the computer must be presented in a fairly rigidly defined style and format known as the command syntax. The correct syntax for every command you use is given in the Command Reference chapter. Program syntax can be compared to the grammar of the English (or any other) language; if you want to make yourself understood to other people you should speak and write reasonably correct English. In the same way, you must be careful to write your BASIC programs correctly for HiSOFT BASIC to understand what you mean. One thing computers are very poor at (and people are normally rather good at) is guessing what we mean when we present them with ambiguous or incomplete instructions (which is why a computer can do many engineering tasks but will probably never have a career in politics). For example we cannot use the commands: LINE 100 100 200 200 LINE 100:100:200:200 LINE (100)(100)(200)(200) and expect the computer to do what we wanted, we must obey its own rules and not invent our own. Try typing in the first line above and running it (Ctrl-X, remember?). You should find that an error requester appears and you are asked to Continue, OK or Stop? Select Stop and you will be returned to your source code window: The editor reporting an error Something has gone wrong, the program did not run. Look to the top right of the window, you should see an error message (Open bracket expected instead of 100) - was that what you expected? HiSOFT BASIC knows what it wants and has told you quite clearly - unfortunately, error messages are not always so obvious. Errors in syntax rather than logic are among the most common mistakes beginners to computing make. Where it can, HiSOFT BASIC will make every attempt to catch these errors and explain to you what it cannot understand (if you think about it, to explain one's own misunderstandings is something of a paradox so please be tolerant if HiSOFT BASIC does not always highlight the exact problem). At this stage it is useful to cover a valuable keyword; the REM or Ôdo nothing' command. REM is actually short for REMark and anything you type on a program line after this keyword will be ignored by HiSOFT BASIC, whether it contains valid keywords or just plain ordinary English. The main use of the REM command is to allow you to add notes to your program which explain what it is the next section of the program is intended to do. REM is also invaluable for catching obscure mistakes (or Ôbugs') in your program where you have got the syntax of the program correct but there is an error in the logic which the compiler may not be able to spot. If you have commented your program clearly it is often possible to quickly isolate and identify where the problem lies. REMs can also be used to Ôswitch off' sections of the program that you have tested and know work correctly, so that the parts still under development can be run in isolation. There is an abbreviation for REM - the Ô (single quote/apostrophe) mark. This is particularly useful for commenting out program lines, because it is quicker to insert. Much more useful than our friends BEEP and LINE, but also more complex, is the PRINT command which is used to get the computer to put information on the screen, as we have already seen above. Extremely impressive tabular effects are possible using the most sophisticated options of this command but, since it is also an instruction that you will be using almost every time you program in BASIC, we might as well learn it quickly - it is after all one of the few means by which we can get the computer to tell us the results of any calculations or tests it has performed. In its simplest form the PRINT command needs no parameters beyond some indication of exactly what it is that you want displayed. One way to try it out is to use the computer as a, rather expensive, pocket calculator. Try entering some of the following short commands. Remember, use Ctrl-X to run the program and Close or AK to clear out the text. For deleting short programs, alternatives to closing the window are: position the text cursor at the end of the line and use the Backspace key repeatedly or position the cursor within the line and use the delete line command, which is Ctrl-Y. PRINT 23+45 PRINT 12*6 PRINT 99/9 Of course it is possible to print text as well as numbers as so: PRINT "Ten times twenty six equals" PRINT 10*26 To signal to BASIC that you want it to print the text as it is given, rather than attempting to look the words up as potential numeric variables (see below) the text must be enclosed in double quotes (""). Again this is an example of the need to use the correct syntax to avoid ambiguity. Layout of Programs Before we get too involved it is worth pointing out some simple points about the layout of BASIC programs. The highly advanced structured programming features provided by HiSOFT BASIC are intended primarily to allow the programmer to work in a clearly defined and modular way. These make it very much easier to follow the logic of what is trying to be achieved. However it is not only the language keywords that make this possible; the way that the listing is organised, on screen or on paper, is very important so that the programs are easy to follow. HiSOFT BASIC does not care whether the keywords you type are in upper or lower case, but they will be much easier for you to spot and to follow if upper case is used; in fact the editor helps you by upper-casing keywords automatically if you check Show Keywords in the Settings... requester on the Settings menu. The editor also does not mind how many extra spaces are used to indent different lines of the listing - make full use of this feature and set out your modules so that the eye can easily follow the hierarchy of the program. Again the editor can help by automatically indenting lines; this is described in more detail later on. Like all modern versions of BASIC it is possible to put more than one command on each line, as long as each command is separated from the next by the colon character (:); this can be very useful to maintain the logic of the program. Unlike many versions of the language, HiSOFT BASIC does not need line numbers; you can use them if you wish or avoid them like the plague! Using Simple Variables The power of a BASIC calculator program such as we have written above can be greatly increased by the use of simple numeric variables. Again there is much more to the subject of variables than we will cover here, but in their simplest form they are just letters or short names that can be assigned a value, and used to represent that value in calculations (just as you would do in algebra, remember that?). Any text string or number that is used explicitly rather than assigned to a variable is a constant. As well as being useful and flexible tools for use in calculations, variables have the useful facility that they can be given descriptive names such as profit and sales which make it very much easier to follow the logic of the computer listing to see what each line is doing. So valuable is this feature that HiSOFT BASIC also allows constants to be given descriptive names as well. To denote that a given name is to be treated as a constant and not a variable we must use the keyword CONST. Try this example: CONST costs% = 1500 sales = 2000 REM Profit is simply sales less costs profit = sales-costs% PRINT "The expected profit will be" PRINT profit Using variables As a simple illustration of the usefulness of variables edit the first two lines of the program to give different values for costs% and sales; do this by placing the cursor at the end of the first line, pressing Backspace to delete the 1500 and entering another value. Repeat this for sales. Run the program and then close the window. Later in this tutorial we will see that the program can be extended to automatically ask you for values for these variables as the program is run. Variables can also be defined that hold text rather than a number. To warn BASIC that this is what you intend to do, syntax demands that all text variable names must end in the $ character. In computer speak Ô$' is often pronounced Ôstring' and represents Ôa string of letters joined together'. The text that you wish to enter into the variable must again be enclosed in quotes. An example is: name$ = "High Quality Software" PRINT "HiSOFT means "; PRINT name$ A quality program... Now run your program. Quality program output... You may have noticed something different about the way that the above program produced its output i.e. it is all contained on one line. This is the result of the semi-colon (Ô;') at the end of the second line. The semi-colon is one of many possible ways we can control the way that PRINT statements are displayed, and these will be detailed later. For the time being though it is useful to know that the semi-colon ensures that any following print statement is added onto the end of the existing one, rather than starting a completely new line. We will be using this feature a lot when we start to look at programs that would otherwise very quickly fill the screen with information. Notice also the trailing space at the end of the second line - we need this to avoid running the words means and High together. The above programs are useful illustrations of how variables work, but they do not really reflect the way that we use them in real programs. The truth is that we often do not know in advance what the value of a variable is to be before the program is run. It is possible, as above, to edit the list of variables and recompile the program every time we use it, but a much more powerful and flexible method is of course to get the computer to ask you for the variable's current value whilst the program is running. This is done using the INPUT keyword. If you use the keyword INPUT followed by a variable name, the computer will display a question mark on the screen and pause the program until it has had an answer from you, as in this example: INPUT x y = x * 10 PRINT "Ten times"; x ; "equals"; y Of course, in a long and complex program we will want to be much more friendly than that - question marks appearing at random to request unspecified information can be rather confusing! The INPUT statement therefore allows you to enter a explanatory line of text that will be displayed together with the question mark. INPUT "What is this year's profit "; x There are of course very many other simple BASIC commands that each achieve a certain effect. There really is no point in detailing each of them here, but look through the Command Reference section and find some more to try. Unless you are very unlucky with your choice, absolutely no harm can be done, but it is just as well to avoid using the commands BLOAD, PEEK and POKE for the time being. Here is an example of how to read the syntax of each command so that you know what HiSOFT BASIC expects. Reading the Syntax INPUT ["prompt"{;|,}] variable_list First of all, INPUT is the actual command and must be present to achieve anything at all. ["prompt"{;|,}] The square brackets ([]) around the whole entry indicate that its use is optional, you can leave it out and the syntax will still be valid. If you choose to use it though, you must enclose a message in double quotes ("") and finish the message with either a semi-colon or a comma. The curly brackets ({}) denote a choice with the vertical bar (|) separating the choices that are available in this case. In fact, using the semi-colon to end the prompt string adds a question mark to the end of the string, while using the comma will not add anything to the string. variable_list Finally, the INPUT statement must be finished with a list of variable names to which you want to assign data - this is not optional. With just these few commands that we have already learnt we can go a long way towards writing programs that show the real power of BASIC and in particular the way computers can handle repetitive and logical tasks with ease. The Heart of a BASIC Program Individual keywords can be strung together to make up quite long and complex commands. These make up the mechanics of your program. However over and above these the essence of most computer software lies in certain programming techniques that bring these commands to life, and structure them together in an intelligent and flexible way. There are really three very simple concepts that lie at the heart of every BASIC program. Once these are understood, learning how any program works will just be a matter of studying the small details. The three concepts are repetition, logical tests & decision making and passing control. Repetition - Loops Computers have certain strengths that make them powerful and extremely flexible tools. In some ways they are also rather stupid; they have to be told everything that they are expected to do. On the other hand they are able to perform these required tasks with great speed and, at least in theory, they will be able to repeat them ad infinitum without ever getting bored and making a mistake. One of the most important types of command we have available in most computer languages is known as a loop. This is the means by which we can make sure that commands can be repeated as many times as necessary without having to type them in over and over again. The most common type of loop seen in BASIC programs is the FOR...NEXT statement. This works by letting you set up a simple numeric variable as a counter which controls the number of times the following instructions are to be repeated. The NEXT command signals the end of the loop and tells the computer to move the counter onto its next value. This is what it can look like in practice: FOR x = 1 TO 6 PRINT "Hello" NEXT x The result will be: Hello Hello Hello Hello Hello Hello Try it out. You can also specify the size of the jumps that the counter makes. Edit the first line to read: FOR x = 1 TO 6 STEP 5 Do this by placing the cursor at the end of the first line and typing STEP 5. You should then see the results: Hello Hello The counter values can of course be set by other variables. Consider this short program: CONST start% = 4, finish% = 12 jump = 2 FOR x=start% TO finish% STEP jump PRINT x NEXT x We have introduced CONST to define one or more constant values and to give them names. This is very useful for writing easy-to-understand programs and you are encouraged to use CONST whenever you have values that will not change over the life of your program. Once defined, you cannot change the value of a named constant. One particularly useful feature of the FOR loop is that we can run one loop inside another using the technique known as nesting. The following illustrates this: Nested loops which produces the following output on the screen: A table using nested loops The second loop runs through in its entirety every time the first loop executes just one step. Note that we have used indentation (i.e. adding spaces or tabs at the beginning of the line) to make the logic of the program more obvious. It is good programming practice to use indentation for statements within loops. In fact HiSOFT BASIC allows you to omit the variable name in the NEXT statement as in: FOR x = 1 TO 10 PRINT x NEXT In this case the computer understands that you are referring to the counter variable x. Wherever an unspecified NEXT statement is found, the compiler will assume that it belongs to the last FOR in the listing. However, we do not advocate this omission of variable names with NEXT as it can cause unnecessary confusion and bugs. Loops are very useful things; they allow you to use the power of the language to automate all sorts of tedious tasks and achieve remarkable effects. If that was all there was to programming, our programs would be very mundane and predictable, doing essentially the very same tasks every time they were run. To make software more sophisticated and useful we have to introduce something else, the element of choice. Programs must be able to make decisions based on the information they have been presented with, ask the user what they are to do next and act on the reply. They must then produce different output in response to the value of certain variables. Decision Making Almost every program you write will use the ability of the computer to rapidly and repeatedly make logical tests and decide upon what action it should do next based on the results. A word processor decides whether to delete a word or insert a letter based on a test of what keys are being pressed; a space invaders game decides whether to Ôfire a missile' based on what is happening to the joystick - it knows whether to blow up an alien based on a test of the relative position of this missile on the screen and so on. There are two main statements that are used to make logical tests within BASIC. These are: IF...THEN...ELSE and SELECT CASE. IF...THEN...ELSE statements are virtually self-explanatory. They take the following form: IF something is true THEN do this ELSE do this instead END IF This really is no different to any (logical) decision we make in everyday life - the computer is just making its own assessment of the current state of affairs and then deciding which instructions to follow subsequently. The END IF command may be unfamiliar to people who have used older versions of BASIC. HiSOFT BASIC allows each set of instructions following the various parts of the structure (IF, THEN and ELSE) to cover several lines of the listing. END IF (note the space between END and IF) is therefore necessary to signal when all of the commands associated with the IF...THEN...ELSE statement have finished, and where the remainder of the program is to continue. It is possible to omit the ELSE part of this structure, which is useful when you want the program to do something if a certain condition is true, but to carry on as normal otherwise. IF...THEN statements can be nested within one another to increase the range of choices using the keyword ELSEIF e.g. IF condition one is true THEN do instruction one ELSEIF condition two is true THEN do instruction two ELSEIF condition three is true THEN do instruction three ELSE do instruction four END IF As you can imagine it can get rather complicated for you to follow the argument and feel confident that the logic used really is correct but clear use of indentation helps greatly. Examples of IF statements follow shortly. A structure that can often be used as an alternative to IF is the SELECT CASE structure which is ideal for allowing the computer to make one from a very wide selection of choices depending on the results of the logical test it has made. Although the exact syntax of the command when you use it will be rather different, you can think of SELECT CASE as representing a situation like (in plain English): SELECT from the following In the CASE where this test is true do this In the CASE where the this test is true do this instead In the CASE where this test is true do something completely different In the CASE where something ELSE is true do this END SELECT (The END SELECT command is essential to let the computer know that it has reached the end of the instructions relating to the last CASE). Before we can go on and give working examples of these commands and their correct syntax, it is important to illustrate exactly how the computer can go about making logical tests. Logical Tests Computer logic resolves all things down to a simple test of Ôtrue' or Ôfalse'. These two possible conditions can be made to represent almost everything you wish, from whether a switch is on or off to whether your bank account is in the black or in the red but, in the end, all the computer cares about is whether the test you have requested results in a Ôtrue' or a Ôfalse' result. BASIC uses the values of 0 for FALSE and -1 for TRUE. Why this is so would take an explanation of how BASIC calculates, and how it holds numbers in memory. We will be tackling these issues in more depth later but if you are ready for them now you have opened the manual at the wrong page. The main point for beginners to bear in mind is that, while the correct explanation of how computer logic works sounds both confusing and highly unlikely, in practice using it is a remarkably simple and obvious process. The use of logical tests in BASIC hinges around the following conditional symbols, known as relational operators, which express the criteria by which the computer is to determine whether the test is Ôtrue' or not. > greater than < less than = equal to <> not equal to >= greater than or equal to <= less than or equal to == almost equal to The last option, ==, will come as a surprise to anyone used to other versions of the language. It is used is to allow text comparisons that ignore any differences in the case of the two strings, and to allow comparison between two floating point numbers - which will be explained later - to ensure that a match is made despite small rounding errors during calculation. It doesn't take a genius to figure out that the comparisons these make must be between two variables, or a variable and a constant - testing between two constants is pointless as it will give the same result every time (although this is occasionally useful to force a result). We can now look at a typical IF...THEN statement - Close your existing source code window and type this in: INPUT "What is your income this year"; income INPUT "What will be your costs"; costs IF income > costs THEN PRINT "Hooray!" PRINT "We're in the money!" ELSE PRINT "Start packing your bags!" PRINT "I'll book the boat to South America." END IF Providing different values for the income and costs will show exactly how the computer's output reflects the result of the test. Try putting a FOR...NEXT loop round the outside of the program to try out 5 different sets of income/cost ratios. Note that, if you use multi-line IF statements, the IF, ELSE and END IF must be on lines by themselves. Here is an example using a SELECT CASE statement to show one version of the correct syntax. To maintain compatibility with as many versions of BASIC as possible this command structure has been implemented in a very flexible way and you are referred to the Command Reference section for a full list of the options. INPUT "A number, please";a SELECT CASE a CASE = 12 PRINT "Number = 12" CASE > 20 PRINT "Number is greater then twenty" CASE <= 4 PRINT "Number is 4 or less" CASE ELSE PRINT "Number hasn't met any of my conditions" END SELECT To extend the usefulness of the above relational operators we have the keywords AND, OR, XOR, EQV and IMP. These are used to compare two separate tests and to make judgements about their relative validity. The first two are again very easy to grasp as in the following examples: IF income > costs AND tax < profit THEN PRINT "Hooray!" END IF The expression will only evaluate as Ôtrue' if both conditional tests are themselves true. IF income > costs OR tax_rebate > 5000 THEN PRINT "Hooray!" END IF The expression in line 1 will evaluate as Ôtrue' if one or other or both of the tests are themselves true. XOR is a trickier one to define as it has no direct comparison in the English language. It essentially stands for eXclusive-OR which means that the expression will evaluate as true if one or the other half is itself true but not if they are both true together e.g. IF income > costs XOR deficit < max_tax_loss THEN PRINT "Hooray!" END IF EQV, short for EQuiValent, is used when two tests are to be made which need to give the same result, whether the result is true or false i.e. x = 1 : y=2 IF x = 10 EQV y = 230 THEN PRINT "Yep" will print Yep because both comparisons give an equivalent result i.e. Ôfalse'. IMP stands for Ôis IMPlied by' This is without question the most abstract of the logical tests. To complicate matters even further it is the only logical operator that puts a strong emphasis on the order in which the two subtests are listed. It can best be understood as a measure of whether or not the result of the second test can be implied by, or concluded from, the result of the first test. The rules are that: * a TRUE result can be concluded from a preceding TRUE result; * a FALSE result can be concluded from a preceding FALSE result; * a FALSE result can be concluded from a preceding TRUE result; * a TRUE result cannot be concluded from a preceding FALSE result. The premise is that it is possible to draw the wrong conclusions from correct assumptions, but it is impossible to draw the correct conclusions from wrong assumptions - try these out: x = 1 : y = 2 IF x<2 IMP y<3 THEN PRINT "Yep" ELSE PRINT "No" IF x=2 IMP y=10 THEN PRINT "Yep" ELSE PRINT "No" IF x=1 IMP y=10 THEN PRINT "Yep" ELSE PRINT "No" IF x=2 IMP y=2 THEN PRINT "Yep" ELSE PRINT "No" The implications of all the above logical operators can be reversed by the use of the keyword NOT as in: IF NOT (x=10 AND y=12) and so on. Testing Text It is of course equally possible to make logical tests on text data. The relational operators we use to express the test are the same as with the numeric examples given above but the way in which the computer evaluates whether the result is TRUE or FALSE is rather more complex. Possibly the simplest to start with is to test whether two text strings are equal (=) or not equal (<>). Obviously this example: test$ = "FRED" IF test$ <> "BILL" THEN BEEP is straightforward. But what about operators such as greater than (>) or less than (<)? How can we say that one word or letter is Ôgreater' than another? To understand the answer we will have to make a diversion into looking at the way computers store letters in their memory. Despite the way they look on screen, computers store letters in their memory as numbers. All modern computers stick to an American convention known as ASCII (which incidentally stands for American Standard Code for Information Interchange) whereby each letter has a certain number associated with it. By standardising things in this way it has made it possible for computers to talk to each other and send text such as Hello Jim without it coming out as ghJJm KPhZz. Note that all lower case letters have higher numbers than all the upper case ones. The digits (0-9) come before both. The letters with a value of 0-31 in the ASCII table are special control characters. Sending these, or a combination of them, to the screen or printer often produces a response such as changing some characteristic of the display, or type style, or moving the cursor. In a logical test, the first character of each of the two strings that are being compared will be tested - if one has a higher ASCII number than the other it will be regarded as Ôgreater'. If both characters have the same ASCII number the computer will move on to the next character in both strings. If both strings are the same for all of their common length except that one is longer than the other, the longer one will be regarded as greater rather than the shorter string. Note also that even a blank space is a character and it comes before all others (value 32 in the ASCII table). A leading space in some of the data is a common way in which text comparisons can become completely confused, and it can be a very difficult problem to spot. Perhaps it all sounds a little complex, but the encouraging thing is that in practice it is easier than it looks. Common sense will usually tell you what the outcome of any test between two strings will be. The only time it is likely to be as clear as mud is when punctuation and unusual characters such as †, ¯, and § are compared, as these tend to occur in unpredictable places in the standard ASCII character set. The relational operator == is particularly valuable when making text comparisons, as it can be used to ignore any differences between the case of the letters being compared e.g. INPUT "Are you happy"; ans$ IF ans$=="Yes" THEN PRINT "I'm glad!" Will give a glad message if you respond with YES, yes, Yes etc. but not if you answer No. Logical Loops We have so far seen how to get the computer to execute a series of instructions a set number of times as a loop. We have also seen how to make it decide, from a series of options, what it is to do next, based on a logical test. The situation often arises where the user simply cannot predict how many times a certain loop will be required to execute and it would be ideal if we could get the computer to use its logic to decide this for itself whilst running. HiSOFT BASIC provides several methods of doing just this, based on special loops that continue to execute until a logical test tells them to stop. (Note that many inferior BASICs which lack these facilities sometimes forced the user to insert a logical test within a FOR...NEXT loop. This test would force an unannounced jump from within the loop and end its execution when the true result was reached. Most authorities would agree that this is bad programming practice although it was often the best way of getting the job done under the circumstances. Unlike some languages, HiSOFT BASIC can handle such Ôjumps' without complaint, but the programs you write will be much clearer if you avoid doing so and use the following structures.) The three logical loops that we can use are known as the WHILE...WEND, REPEAT...END REPEAT and DO...LOOP statements. The first one works like this. The WHILE loop WHILE conditional test is true do this do this WEND The WEND keyword simply signals the end of the instructions that are to be included within the loop. Here is an example: INPUT "What is your bank balance"; balance income = 50 month = 1 WHILE (balance>0) AND (income>49) PRINT "What is the income for month"; month; INPUT income PRINT "What are the costs for month"; month; INPUT costs balance = balance + (income - costs) PRINT "New balance is"; balance month = month + 1 WEND PRINT "It's time to look at getting a proper job!" Note that, in order to ensure that the WHILE loop is executed at least once, we have had to initialise income to 50 (>49) before the beginning of the loop. In fact, it would be better, therefore, to use a different type of loop, the DO loop, for this example and we shall now see why. The DO loop The DO loop actually has several permutations which make it a very much more flexible construction than the WHILE loop. In fact it even has an option that will completely simulate the WHILE...WEND command. The various permutations include: DO this instruction that instruction LOOP UNTIL condition DO UNTIL condition this instruction that instruction LOOP Can you see that these constructs are similar to a WHILE loop? Are there any differences? Yes ... a DO UNTIL...LOOP executes the instructions in the loop until the condition becomes true whereas a WHILE loop executes until the condition becomes false. Also DO...LOOP UNTIL checks its exit condition at the end of the loop, not at the beginning. Thus, in the previous example, since we did not know the value of income at the start of the loop, perhaps we should have used a DO...LOOP UNTIL loop, not a WHILE loop - can you re-write it? There are other permutations of DO: DO WHILE condition this instruction that instruction LOOP DO this instruction that instruction LOOP WHILE condition As we said above, the logic of the WHILE comparison is opposite to that of the UNTIL option: DO UNTIL x = 10 and DO WHILE x <> 10 will produce exactly the same effect. The most general form of the DO loop is: DO this instruction that instruction LOOP This command structure will continue to run and run unless there is a logical test inserted somewhere within it terminating in an EXIT DO or EXIT LOOP command; these two are interchangeable. It is again possible to nest such loops several times, and indeed to nest one type inside another. It is also possible to have conditional tests at both ends of a DO...LOOP. The REPEAT loop The third type of logical loop is the REPEAT...END REPEAT structure. This has the most in common with the final form of the DO...LOOP that we have just seen, in that there is no builtin way for the loop to terminate beyond the inclusion of one or several EXIT statements. The difference between the two types of loop hinges on the fact that each REPEAT loop you create can be given its own name. Several such loops can be nested culminating in a series of logical tests with EXIT statements. Any EXIT statement can itself refer to the name of a given loop, but not necessarily the one that was most recently defined. In other words you can EXIT a named outer loop from within an inner one; very useful for aborting some task if a catastrophic error occurs. We advise the use of DO...LOOPs wherever possible since they have a much more coherent structure - try to avoid REPEAT loops unless absolutely necessary. Passing Control Within a Program The effect of logical tests is to force the program to make a decision about which lines of the program are to be executed next. In other words the flow of the program is passed from one section to another. Most large programs contain sections that are only executed as and when certain conditions are met. A space invaders game for example will have a GAME OVER message that will only be displayed when the program has realised that you have had all of your missile bases destroyed. As long as you can continue playing unscathed, control of the computer will never pass to the end-game section of the program. In the above examples the logical tests have passed control to one or two lines of the program, for very simple results. However there is no reason why there should not be enormously long and complex sections of the program that are accessed in each case. There are two distinct mechanisms that we can use to cause the computer to move to another section of the program. These are jumps and subroutine calls. The difference between the two is that when the computer jumps to a different part of the program it does not bother to remember where it came from and it is not able, and does not try, to get back there unless you explicitly tell it where to go. By contrast, subroutines and sub-programs are self contained sections of the program that fulfil a specific task and then automatically return back to the next instruction following the one that first called them. In older versions of BASIC, versions that required the user to begin each line with a number, jumps were made by simply entering GOTO line number as in the following example. 10 LET x = 1 20 LET y = 20 30 LET x = x + 1 40 IF x = y THEN BEEP ELSE GOTO 30 This example also shows how very primitive logical loops can be created - the program will jump between lines 30 and 40 until the test is passed as true. Although it is capable of running the above program, HiSOFT BASIC does not force the programmer to use line numbers. But in that case how would we signal to the program when it is to make a jump, and where it is expected to go to? The answer is through the use of line labels, which are essentially names that we give to chosen lines in the program, usually lines that start a certain routine. By making a jump to one of these names the same effect is achieved as with a line number jump but the listing is made much more readable. Line labels are entered by just typing the required name followed by a colon. maingame: . . IF missile_base = 0 THEN GOTO endgame . . endgame: BEEP PRINT "Game over." In older versions of BASIC, subroutines were similarly accessed by a call to a certain line number which was to be the beginning of the routine itself. The keyword that makes this call is GOSUB. Here is an example: 10 INPUT "What is your bank balance"; x 20 IF x >0 THEN GOSUB 100 ELSE GOSUB 200 30 INPUT "How much will your bills be this week"; z 40 IF z > x THEN GOSUB 200 ... 100 BEEP 110 PRINT "There will be no bank charges" 120 RETURN ... 200 BEEP 210 PRINT "There will be bank charges" 220 PRINT "unless you pay some money in" 230 INPUT "How much can you pay in"; y 240 IF y < x THEN PRINT "Not enough I'm afraid" 250 END IF 260 LET x=x+y 270 RETURN The keyword RETURN is essential to signal the end of the current subroutine, otherwise the subroutine would carry on ad infinitum. The above example appears slightly laboured because in very short programs it can be difficult to justify the use of subroutines as separate entities from the main lines of the listing. In long and complex programs however they perform several invaluable roles. Firstly they allow the programmer to divide up his work into a series of logical modules, each one of which can be worked on, altered, tested and debugged in isolation. As well as making things easier for the author of the program, they also make the listing very much easier to follow and alter at a later date. As an extension of this philosophy, once a subroutine has been perfected it can be made part of a stored Ôsource library' of routines which can, at any stage, be slotted into new programs that are under development. For example there may be a subroutine that plays a tune which you can use in any game you write, or you may have some lines which place a menu of options on a screen, or which reads data from a disk file. Another important use for them is to make the actual listing of your programs more compact and economical. Any program routines that are used several times within the same listing need only be typed once, and they can then be accessed by the appropriate subroutine call as often as necessary. HiSOFT BASIC will of course allow the use of simple subroutines as described above but in addition, as with GOTO, it is possible for the routine to be called by a name rather than by its line number, which of course makes the listing easier to follow. Avoiding numbers also makes it logistically simpler to merge many different library procedures together into a new program. Another improvement is that the RETURN command can be followed by a line number or label that will allow the program flow to be passed to yet another part of the listing. A Better Way; Sub-programs As well as standard subroutines, there is a much more satisfactory way of organising programs into modules which are called sub-programs. Sub-programs have three advantages over subroutines, which may not be appreciated by many programming beginners but will become more and more important as your skills increase together with the complexity of your programs. These are: * local variables * parameter passing * recursion Each of these will be explained as we progress with the tutorial. The keywords used for defining sub-programs are SUB and END SUB. Once defined they can be CALLed from anywhere else in your program listing. If you have been paying attention until now, you should find it fairly easy to decipher the following example... Using a sub-program ... Try typing this in and running it. It has deficiencies; it only works properly using the standard fonts; the sub-program should be passed, as two parameters, the point at which the text has been drawn and it should then work out where to put the box ... but you see the idea. ... to get HiSOFT in a box! Note the LOCATE x,y statement, after Mainprogram:, which positions the cursor at row x, column y within the window, so that the next PRINT takes place there. The origin is 1,1 not 0,0 as it is for graphics statements. Here's a chance to show off a useful feature of the editor; you often want to use a sub-program or a function in many different programs - after all, that's one of the main reasons for coding them in the first place. You could group all your sub-programs together in one program, save it and Ôinclude' it in whichever program was going to use the sub-program, function etc. However, there is an easier way that you may wish to use sometimes - simply cut the sub-program from one window and paste it into another, here's how: Position the cursor at the start of the REM statement, click and hold the button down. Now drag the mouse until you have marked all the text up to the end of the END SUB and release the mouse button. Marking a block Now select Copy from the Edit menu; this copies the marked text into memory. Cut, copy and paste Now open a new window (AN) and select Paste from the Edit menu. The complete sub-program is copied into your new window, ready for use. Back to sub-programs ... It is not uncommon to find well written modular programs where the main body of the listing is no more than a series of calls to different subprograms as in the following: CALL start CALL input CALL output CALL end You can invoke a sub-program called fred either by using CALL fred or simply using the name of the sub-program, fred. In this way routines can be executed just by using their names, almost as if they were special BASIC keywords that you have written yourself. The Command Reference section gives more details. However, we would like to stress one point here: CALL fred (john) is not the same as fred (john) When using CALL, you must enclose the parameters within parentheses and they are then passed as variable parameters (see below). However, when not using CALL, enclosing parameters in parentheses forces the parameters to be passed by value. Don't blame HiSOFT. It's historical! More of sub-programs later. Choosing where to go A useful command structure that allows us to program jumps to different routines in a very concise way is ON x GOTO/GOSUB. In this case x is a numeric variable that can contain a number generated by the program, or entered by the user. The command sequence tests the value of x (or rather the integer value of x - see later) and will jump to the xth entry in a list of subroutine names, line labels or line numbers that can follow the GOTO or GOSUB command. This list can be up to 56 items long and can mix line or subroutine names and numbers. Mainprogram: REM Other lines of the program PRINT "Data listed on screen or printer?" PRINT "Screen.......1" PRINT "Printer......2" DO BEEP INPUT answer LOOP UNTIL (answer = 1) OR (answer = 2) ON answer GOSUB screenprint, paperprint Again the example looks like a rather longwinded way to achieve a simple effect but in complex programs this command structure is invaluable. The chosen variable in your own program can be used as an indicator (in computer jargon: a flag) of what routines have been accessed or what choices have been made by the user and the program response tailored to fit. It is not possible to call sub-programs with this command; to do this we recommend you use a SELECT CASE structure with CALLs which would look like: Mainprogram: REM Other lines of the program PRINT "Data listed on screen or printer?" PRINT "Screen.......1" PRINT "Printer......2" DO INPUT answer SELECT CASE answer CASE 1 : CALL screenprint CASE 2 : CALL paperprint CASE ELSE BEEP END SELECT LOOP UNTIL (answer = 1) OR (answer = 2) In general, we would advocate the use of CASE over ON GOTO/GOSUB since it normally leads to clearer programs. Sub-programs As we have said above, sub-programs are much more powerful than old-fashioned subroutines and we encourage you to use them whenever you can. Sub-programs have the ability to define and use variables that are only valid within the routine itself, but which have no effect on other variables of the same name used elsewhere in other routines or within the main program body. These are what are known as local variables, because they are only recognised and used within one routine. They make it immensely easier to write and test procedures as independent modules without worrying about compatibility with others that do different tasks. Say that we want to write a sub-program that takes a text string and prints it ten times on the screen. Our sub-program may be of the following form: SUB Mul_Print FOR x = 1 to 10 PRINT A$ NEXT x END SUB Do not try entering this routine just yet - we will see shortly that it is not quite finished. Firstly, note that we have introduced a variable, x, that is used in the FOR...NEXT loop as the loop counter - it is obviously only needed while the loop is being executed and is therefore a prime candidate for being a local variable - we make it such by adding the statement STATIC x after the SUB definition. We have used the keyword STATIC to declare x as local to this sub-program - it will not be available outside the sub-program (but see SHARED later). If you are peachy-keen you may have noticed the keyword LOCAL in the Reserved Words list or the Command Reference and you may be wondering why we have not declared x as LOCAL. This is because STATIC x is more efficient on memory and runs faster - LOCAL x introduces a new variable every time it is encountered whereas STATIC x creates only one variable which is re-used. The main occasion when you might want to use LOCAL rather than STATIC is if you have a sub-program or function that calls itself and you need independent local variables within each call. Also, we have written the sub-program using the text variable name of A$. However to satisfy the requirements of flexibility and independence from the main program variables we have to find a way to tell the sub-program exactly what A$ represents when we call it. The situation may be that we may not have used that variable name in the main program, or even that we have used it for something completely different. Let's write some code that might call Mul_Print: Main_Program: A$ = "Banana" B$ = "HiSOFT = HIgh quality SOFTware" PRINT "MENU" PRINT "1. "; A$ PRINT "2. "; B$ INPUT x SELECT CASE x CASE 1 : choice$=A$ CASE 2 : choice$=B$ CASE ELSE choice$="Not given" END SELECT The next thing we want to do in this program is to make a call to the Mul_Print sub-program. We need to let this sub-program know that we want it to print choice$ rather than A$, even though we have used the name A$ in the actual Mul_Print routine. The SUB was possibly written months before and stored in a library. Our old type of subroutine detailed above will have been unable to allow for this necessary degree of flexibility. However when using sub-programs we can do it with ease thanks to a technique known as parameter passing. To use this we first have to alter the first line of our sub-program to contain not only the name of the procedure, but also the parameters it will expect to be given from the main program. In our example this would be: SUB Mul_Print(A$) STATIC x FOR x = 1 to 10 PRINT A$ NEXT x END SUB When we actually make the call to the sub-program from the main program we have to also include in that line the appropriate variables, numbers or text strings that we want each passed variable to represent. In our example this line would be: Mul_Print choice$ Not surprisingly the parameters that are passed must match the parameters expected by the sub-program in number, type and order but they need not match with regard to the actual names used. The complete program would be: SUB Mul_Print(A$) STATIC x FOR x = 1 to 10 PRINT A$ NEXT x END SUB Main_Program: A$ = "Banana" B$ = "HISOFT = HIgh quality SOFTware" PRINT "Menu" PRINT "1. "; A$ PRINT "2. "; B$ INPUT x SELECT CASE x CASE 1 choice$=A$ CASE 2 choice$=B$ CASE ELSE choice$="Not given" END SELECT Mul_Print choice$ Not particularly useful, but it illustrates the point. Note how the sub-program is defined before the main program - this is good programming practice, but not essential. Now try editing the main program to include a second option for choosing how many times the string is to print. The Mul_Print sub-program will have to have a second parameter entered into the opening line, say Mul_Num which will be used in the line: For x = 1 to Mul_Num Remember to put a second parameter in the call to the sub-program to pass the value for this variable that has been selected by the user. By default HiSOFT BASIC assumes that all variables that are declared or used by a sub-program are STATIC variables i.e. are local to the sub-program and will not affect or be affected by the values of variables of the same name elsewhere in your program. STATIC variables are zeroed when your program is first executed but are then left alone by HiSOFT BASIC - their values are held static until you change them within your program. However, even though STATIC is the default, it is advisable to explicitly declare any local variables as STATIC (or LOCAL) since this improves the readability and maintainability of your program. Also, if you have variable checks on (a compiler option), an error will be reported if you use variables in a sub-program or function that have not been declared. However there are situations where it would be valuable for the computer to create a new variable each time a sub-program is called. If you want HiSOFT BASIC to create a new variable for each sub-program call then you should declare the variable as LOCAL, not STATIC. There is also a way of letting a sub-program act on and even change the values of variables that are used by the main program and without these having to be passed to the subprogram as parameters. We do this by stating, before they are declared or used by the sub-program, that a certain variable name is to be SHARED. As you will be able to deduce from the example programs we have used so far HiSOFT BASIC does not force you to define exactly which variables belong in which categories. The compiler is able to deduce the effect that you are trying to achieve unless there is a mistake in your own logic. For that very reason it is good programming practice to work out and specify exactly which variables belong to which types in your program. SUB testproc SHARED x,y PRINT x, y , z x = x + 10 y = y + 10 z = z + 10 END SUB Mainprogram: x = 10 : y = 20 : z = 50 PRINT x, y, z testproc PRINT x, y, z This will produce the following output: 10 20 50 10 20 0 20 30 50 which illustrates that x and y were usable, and capable of being changed, by the sub-program even though they were not passed as parameters. The third variable, z, was in fact treated as two entirely separate creatures because, by default, it was STATIC to the sub-program. x and y were created by, and belong to, the main program unless SHARED; z belongs exclusively to the sub-program. Remember, the default situation is that every variable used in a sub-program is regarded as STATIC unless specified otherwise. The use of variables that are SHARED between sub-programs and the main program should be treated with extreme caution since it is easy to introduce obscure bugs using SHARED variables - see the SHARED variables section in the Concepts chapter for an example. SHARED variables are not the only means by such a sub-program can pass information or changes back to the main program body and this brings us on to a discussion about Value Parameters and Variable Parameters. Value and Variable Parameters There are two different ways that you can pass parameters to and from sub-programs with HiSOFT BASIC; essentially you can either pass just the value of the parameter to the sub-program or you can pass a reference to the parameter. In the latter case the parameter is called a variable parameter because the sub-program can actually modify the value of the variable since it knows where to find it - all parameters passed to sub-programs are, by default, variable parameters. This provides another mechanism, along with SHARED variables, for sub-programs to communicate with the main program and each other. For example: SUB Strip_Spaces(a$) STATIC b$,i,j b$="" REM Find first non-space character i=0 DO i=i+1 LOOP UNTIL MID$(a$,i,1)<>" " REM Now copy rest of string to temporary string FOR j=i to LEN(a$) b$=b$+MID$(a$,j,1) NEXT j REM Copy back to passed string a$=b$ END SUB test$=" HiSOFT" Strip_Spaces test$ PRINT test$ Try this for yourself; it shows the use of a variable parameter to strip the leading spaces from a string variable. We have used a few string functions (like MID$ and LEN$) that you may not have seen before - don't worry, they will be explained later. In fact, there is a built-in function called LTRIM$ which will strip leading spaces for you - see the Command Reference. HiSOFT BASIC allows you to override the default that a parameter is a variable parameter - simply enclose the parameter in parentheses in the call to the sub-program e.g. if we had used Strip_Spaces(test$) above, test$ would have remained as HiSOFT. If, instead, you use CALL to invoke the sub-program and you want the parameter passed by value when it has not been declared as such in the sub-program definition then, again, enclose the parameter in parentheses, for example: CALL Strip_Spaces ((test$)) The alternative to variable parameters is the value parameter where only the value of the variable is passed to the sub-program and the variable itself cannot be modified. To indicate that you want a variable to be passed by value, you should precede it with the word BYVAL or VAL in the sub-program definition. For example: CONST FALSE=0 SUB Factors(BYVAL Number) STATIC i FOR i=2 TO Number/2 IF Number MOD i=0 THEN PRINT i NEXT i END SUB DO INPUT i Factors i LOOP UNTIL FALSE There is no need to make Number a value parameter in this example since it is not modified within the sub-program - but value parameters are processed considerably faster than variable parameters and, in a calculation-intensive (albeit simple) sub-program like this one, speed can be of paramount importance. So it is advisable to define your parameters as value parameters unless you need them to be otherwise. Note also the use of CONST to define a constant FALSE that can then be used with the DO...LOOP to loop forever. Type in this program and run it - when you get bored hold down Ctrl-C to break out of the program. Now for a little fun... Recursion Sub-programs do of course have the ability to call other sub-programs in turn, or even to call themselves if necessary. The level of complexity of such inter-related calls can be enormous, yet the resulting listing will remain obvious and easy to follow because the procedure name can almost be regarded as a brand new keyword that does a certain job - contrast that with a similar situation using line numbers. The ability of a sub-program to call itself is known as recursion. This is an enormously useful and powerful programming tool that opens up whole new worlds of opportunity and in doing so flies completely over the head of 90% of computer users. Unfortunately the programs used to demonstrate the principle usually employ it to solve complex and obscure mathematical problems that are far from easy to follow. We are not intending to let you off the hook either - on the HiSOFT BASIC disk you will see a version of the infamous ÔTowers of Hanoi' program, called Hanoi.bas. This demonstrates the full power of recursion by solving the sort of horrific mind bending puzzle that most people cannot visualise anyway and you should certainly study it when you have finished working through this book. However, here is a procedure that demonstrates recursion, at least to the extent of proving that it really does work. Like many recursive problems the same or a similar effect can be achieved by the use of logical loops, but you will often find that using procedures in this way is often much more efficient in both speed and compactness of your program. Type this in: Ô The ForWarD sub-program. Draws a line of length ' r in the direction, dir, of the Ôturtle', ' from its current position SUB FWD(BYVAL r) SHARED curx, cury, dir STATIC newx, newy ' Calculate the new x and y co-ordinates newx = curx + r * COS(dir) newy = cury + r * SIN(dir) ' Draw the line LINE (curx, cury) - (newx, newy) ' Update the (x, y) position of the turtle curx = newx cury = newy END SUB Ô Turn the turtle through r degress ' i.e. simply change dir SUB RIGHT(BYVAL r) SHARED dir dir = dir - r / 180 * 3.1415926 END SUB Ô Use FWD and RIGHT to draw a spiral ' this sub-program is recursive, it calls ' itself to draw successive, longer lines ' to produce a spiral SUB spirals(BYVAL L, BYVAL A) FWD L RIGHT A IF L < 150 THEN spirals L + 1, A END SUB Ô Initialise the turtle and draw a spiral ' You can try changing the numbers 9, 95 to ' obtain different shapes main: curx = 160 : cury = 100 : dir = 0 spirals 9, 95 END The above program produces the output shown below: A recursive turtle Beginners may feel that they have been given a fairly rough ride over the last few sub-sections so let's get back to some simpler BASIC concepts for a while. Functions Functions are an extremely important group of part of HiSOFT BASIC that encompass almost every type of task you can wish your computer to do, they can control graphics, text, calculation, logic, indeed there are very few programs that do not use functions of some sort in almost every line. Any attempt to define what functions do is therefore almost pointless - they can do anything and everything. What makes a function a function as distinct from any other element of BASIC is that, once called, it has to return some form of information, text or numeric data, to the original program. Most, but certainly not all, functions need some information to be passed to them from the main program to operate on. Because of their flexibility we will only briefly run through some of the different types of functions here, the majority of them will be covered as and when they come up under different logical headings in the tutorial and some will be left for you to discover in the Command Reference section. Mathematical Functions We have already seen how the use of different arithmetic signs, add, divide, multiply and divide can make your computer into a kind of calculator. To extend the usefulness of these HiSOFT BASIC comes with an extensive range of mathematical functions. COS and LOG are two examples: PRINT COS(45) y = LOG(x) : PRINT y Text Functions Most text functions are designed to perform operations on existing text strings. They will therefore be covered in depth in the section Strings and Things. Exceptions include two text functions that are designed to create new strings - SPACE$ and STRING$. A$ = SPACE$(10) will create a new string of ten spaces, which can be printed or assigned to a variable. STRING$ can be used to produce a string of specified length made up of a chosen character repeated: x = 23 PRINT STRING$(x,"*") which will print: *********************** Miscellaneous Functions There are a wide selection of functions available that fit into no neat category, but just perform a useful job. Two simple examples are DATE$ and TIME$ which return the appropriate values from the computer for display within your own programs. PRINT DATE$ may give the answer 22-04-1992 Remember that a function, by definition, must return some sort of value or data to the program that called it. User Defined Functions HiSOFT BASIC has a very generous smattering of functions designed to provide you with a wide selection of useful tools for manipulating data. However it is impossible to predict or to cater for every possible eventuality that will come up. There will inevitably be times when you will want to use functions that do not come supplied. Fortunately you can then employ User Defined Functions to construct new routines to meet your own requirements. HiSOFT BASIC differs from many other versions of the language in that it allows the user to write complex multiple-line functions. These have many similarities with sub-programs. They can have parameters passed to them (and by definition must pass a result back to the calling program line). Also, they can use local and global variables and as such can easily be stored in a pre-written library of routines. The keywords used to set up and call a user-defined function are FUNCTION / END FUNCTION or DEF FN / END DEF / FN. As with sub-programs and logical loops the command EXIT FUNCTION / EXIT DEF can be used to trigger a jump from the function, e.g. if it has been passed a value that is outside of the range that you wish to permit. The name that is used to define a function as in the commands: DEF FNname or FUNCTION Cube is also the name used to call the function using the command: FNname or cube and is also used as a variable, within the function definition, which is assigned the value that is to be returned to the calling program line. Note that there are essentially two ways of defining a function; using the FN terminology or, more simply, using the FUNCTION keyword; we encourage you to use the latter, more modern method. Here is an example of a mathematical function that can be used to convert inches into centimetres. FUNCTION metric(a) metric = a*2.54 END FUNCTION Mainprogram: Inches = 10 CMs = metric(Inches) PRINT Inches; "inches equals";CMs;"centimetres" The following example of a text function can be used to strip trailing and leading spaces from any string. It employs one or two pre-defined text functions as building blocks; to follow these you must refer forward to the section on strings, or consult the Command Reference section. FUNCTION strip$(BYVAL A$) IF LEN(A$)=0 THEN EXIT FUNCTION WHILE LEFT$(A$,1)=" " Length=LEN(A$) A$=RIGHT$(A$,Length-1) WEND WHILE RIGHT$(A$,1)=" " Length=LEN(A$) A$=LEFT$(A$,Length-1) WEND strip$=A$ END FUNCTION The above example is useful as an exercise but HiSOFT BASIC lets you strip leading and trailing spaces with LTRIM$(RTRIM$(A$))! If you use a function, declared using FUNCTION, before it is defined, you must tell HiSOFT BASIC that you are going to do this by using DECLARE FUNCTION name followed by its parameter list, at the front of your program. Otherwise the compiler may think that you are using an array name and not a function call e.g. DECLARE FUNCTION Cube (BYVAL x) PRINT Cube(5.6) FUNCTION Cube(BYVAL x) Cube=x*x*x END FUNCTION In general, it is a good idea to include all your sub-program and function definitions at the beginning of your program. Note that parameters passed to user-defined functions defined using DEF FN are, by default, value parameters - you can force a variable parameter by including the word VARPTR before the variable in the function definition. Also local variables within a DEF FN function are automatically assumed SHARED. This is all historical and we have included this behaviour in HiSOFT BASIC to be compatible with Microsoft QuickBASIC et al. Functions declared using the keyword FUNCTION do not suffer from this confusing behaviour towards parameters and local variables - they behave in exactly the same way as sub-programs; parameters are, by default, variable and local variables are, by default, STATIC. Therefore we advise you, most strongly, to use FUNCTION and not DEF FN, which is provided only for backward compatibility. More about Numbers & Text The ways that HiSOFT BASIC handles data, particularly numbers, is very much more complex than we have seen so far. This is not sadism on the part of the designers but is in fact essential (honest!). In every day conversation we all use numbers in a variety of ways and, most of the time, other people manage to work out exactly what we mean. For example you may say to someone ÔI'll be around in ten minutes' and it is understood that you could in fact be eight, twelve or even twenty minutes. Conversely if you say ÔI want to collect that ten pounds you owe me' you will be rather unhappy to receive any less than that. The essential difference between the two is the precision of the figures we use and the implicit understanding of the person to whom we are communicating. The problem is that while people are able to use their experience to interpret what you probably mean, a computer takes everything much more literally. It will expect you to turn up in exactly 600 seconds to collect your 1000 pence. As if that was not enough, even computers, which insist that you say exactly what you mean, do not always mean exactly what they appear to say. What would you make of a calculator that claimed that two plus two did not equal four. Not much probably, but it is actually possible for this to happen. Numbers that look quite simple to the user, such as Ô4', may in fact be stored as very much longer figures e.g. Ô4.00012'. These slight aberrations from what the user sees, or means when typing in figures, result from minute rounding errors in the way that the calculations are performed internally. Obviously, this state of affairs can not be left to operate randomly. Small rounding errors of this sort, when added together or multiplied can eventually result in visibly different answers from that which is expected. We therefore have different ways of expressing numbers and numeric variables in HiSOFT BASIC that will control the degree of precision that you want the calculations to work to, and can force numbers to change their internal format to ensure that they achieve the desired result. The most important distinction is between integer and floating point numbers. An integer number is a whole number, i.e. there can be no decimal fraction. By forcing the computer to use integer numbers we can ensure that all calculations produce the result that we would expect, 2+2 really does equal 4. Integers are particularly useful when performing financial calculations where it is important that the odd penny does not go astray. Unfortunately, for unimportant reasons which have to do with the way that computers work, integer numbers usually have to fit within the range +32767 to -32768. In order to maintain compatibility with programs that run under other versions of BASIC, the HiSOFT BASIC recognises and uses the ordinary integer type and the associated commands that allow variables to be assigned as integers. However this version of the language has also been extended to also allow long integers, whole numbers in the range 2147483647 to -2147483648. Floating point numbers are any numbers that have a fractional part, i.e. there is a value after the decimal point. Again there are two different types of these numbers - single and double precision numbers. Floating point numbers are often displayed using scientific notation consisting of a fixed point number (the mantissa) followed by a letter E and then an integer which is the exponent. To convert this notation to a normal format you have to multiply the mantissa by ten to the power of the exponent. Single precision floating point numbers are accurate to seven digits in the mantissa while double precision numbers are accurate to 16 digits in the mantissa. The exact ranges for these numbers are given in the Concepts chapter. For small number values, where there is no ambiguity in the precision, numbers will be displayed in the normal form. For large numbers scientific notation will be used on screen. Floating point numbers, and in particular those with double precision, use more memory and are slower to calculate than long integers, long integers are slower and use more memory than integers. However the power and speed of HiSOFT BASIC together with the large amount of available memory on the Amiga range means that these problems are much less noticeable than on other micros. You will have seen by now that HiSOFT BASIC does not force you to use any of these number format commands. As with many other commands, the language assumes a default setting of single precision if your exact requirements are not specified. There are also several associated commands that allow numbers of one type to be converted to another, for calculation or display. It is possible to denote explicitly which type a number or numeric variable belongs to by the use of a certain suffix. The % character denotes an integer. The & character denotes a long integer. The ! character denotes a single precision number. The # character denotes a double precision number. The suffix can be given as part of a number as in: x = 234% or as part of the variable name in which case that particular variable is unable to store data of the inappropriate type: x! = 2.34 Constant literals, i.e. those that are not assigned to variables and which therefore will not change during the running of the program can again be integer, long integer, single or double precision floating point. Of course any number or variable that looks like an integer can be forced to be stored internally as a floating point number by use of the appropriate suffix. A number or variable that has a fractional part can be forced to be an integer by assigning it to a variable with the % or & suffix and if this is done the number will be rounded to the nearest integer. In the course of a calculation all numbers are converted to the same format as the highest precision number used anywhere in the current operation. The answer is also returned in a form that matches that precision. For example in the line: x& = 100000& + i%*i% i%*i% is evaluated as an integer and will overflow if i% is greater than 181. The result is added to 100000& and this result is then turned into a long integer. If an integer number, assigned to an untyped variable, undergoes a calculation such that it is converted to a number with a decimal fraction, i.e. the variable value is automatically converted to floating point. If the number is assigned to a typed variable the result of the calculation will be amended to fit that type. There are also several associated commands that allow numbers of one type to be converted to another, for calculation or display, or which control the type of number a variable is capable of holding. The following commands can be used to predefine what types of data a given variable name will store. They work by specifying the range of letters that are to begin the variable names for a given data type. For example: DEFDBL A-C, F will mean that all variables that begin with the letters A, B, C and F will all be assigned double precision values even though in the actual statement that gives the value to the variable the number could look like single precision or even an integer, without any # sign. DEFSNG range will predefine a range of variable names to take single precision numbers, DEFINT range will define them to take integers and DEFLNG range to take long integers. You can also use DEFSTR range to define variables to hold strings; this allows you to dispense with the $ suffix for the relevant string variables although, of course, you must still include the $ for MID$, LEFT$, LTRIM$ etc. All of the above definition commands are useful for freeing you from the need to explicitly define a variable type as it is being used, but they can be over-ridden by the use of the data suffixes described above. They are particularly valuable for catching input from the user that is required to be of a particular numeric type. There are a suite of commands that change the internal format of a given numeric variable into another type. CSNG(x) converts x to a single precision number, CDBL(x) converts x to a double precision number, CINT(x) converts x to an integer and CLNG(x) converts x to a long integer. In both of the latter cases the fractional parts are rounded to the nearest integer. Try this: x=22/7 PRINT CSNG(x), CDBL(x), CINT(x), CLNG(x) CLNG is useful for promoting the result of a short integer calculation. As we said above, 100000& + i%*i% will overflow if i% > 181 but 100000& + CLNG(i%)*i% will allow a much wider range of values for i%. The following commands are functions that act on a given variable, to return the integer part of that variable, although as you can see there are subtle differences in the way they work. These functions can be used to assign the integer part to a new variable, to print it on the screen or to allow it to be used in a calculation. FIX(x) returns the truncated integer part of the number, x, i.e. if x is 1.2, FIX(x) returns 1. Similarly if x is 22.99, FIX(x) will return 22 - it always rounds down with positive numbers. For negative numbers FIX still truncates the figures such that the overall effect is one of rounding up. If x is -22.99, FIX(x) will return -22. INT(x) returns the largest integer less than or equal to x. For positive numbers the effect is the same as FIX(x) but for negative numbers INT rounds down. If x is -22.99, INT(x) will return -23. Finally there are two special arithmetic operators - MOD and \ (backslash) which are used for integer division. In an arithmetic expression such as 20.4\5.2 the backslash command stands for integer division such that both numbers are rounded to the nearest integers before the division takes place. The answer is also an integer value. The keyword MOD returns the remainder of an integer division as an integer. 33.2 MOD 5 would return the value 3. Yet More Numbers - Bases There is one more aspect of the way that numbers are stored and used that needs to be covered. In conversation we use exclusively decimal numbers, each column in a large number represents ten times the number to the right. This is known as base ten arithmetic. However there is no reason why we have to be restricted just to that system. In their deepest darkest parts computers work exclusively in binary or base two arithmetic. Binary digits are either on or off and these two available options are used to build up larger and larger numbers. Each column in a large number represents twice the column to the right of it. In binary arithmetic 0 stands for 0, 1 for 1, 10 for 2, 11 for 3, 100 for 4, 101 for 5, 110 for 6, 111 for 7, 1000 for 8 etc. However binary arithmetic soon gets unwieldy for people to use as the numbers increase in length rapidly, and are almost impossible to identify by just casual inspection. Programmers therefore tend to use the larger base eight (octal) arithmetic or base sixteen (hexadecimal) arithmetic. Because eight and sixteen are both powers of the number two, both octal and hexadecimal numbers have much more in common with the way that computers actually work than ordinary decimal numbers, and allow us to see patterns in the data that are meaningful to the computer. Because these number bases are usually used for direct manipulation of the computer memory or similar, and would hardly ever be used for other calculations, we do not make provision to use fractional parts of numbers expressed in octal or hexadecimal - they are all expressed as the equivalents of decimal integers. The command OCT$(x) will convert the (rounded) integer part of the number x to its octal equivalent. The result is stored as a string variable to avoid ambiguity, i.e. to ensure that calculations can not be attempted directly with octal numbers. HEX$(x) provides a similar conversion to hexadecimal. We have a problem with hexadecimal numbers that does not crop up in any base less than ten. We need a system for expressing numbers in the range 10-15 with just one figure. The convention used to do this is to use the letter A to stand for 10, B for 11, C for 12, D for 13, E for 14 and F for 15. So 8 = 8, B = 11, 10 = 16, 15 = 21, 1A = 26 and FF = 255. Note for example that the total number of ASCII codes range from 0-255 and 255 is the binary number 11111111, and the hexadecimal number FF (both of which look much more significant than 255). This demonstrates how using other bases can be helpful in denoting meaningful numbers. HiSOFT BASIC allows the use of prefixes &B, &H and &O (O as in "Oh" rather than "Zero"!) for specifying binary, hexadecimal and octal constants respectively - see the Concepts chapter for a full discussion of these different base constants. Having broached the issue of how computers store and use numbers internally, it is probably worth pushing on with the subject as some terms will inevitably come up in later parts of the tutorial. The following brief explanation is not essential reading at this stage but will become increasingly important as your programs become more sophisticated. How Computer Numbers Work Computers store all numbers and data internally in units known as bytes. A byte is an eight digit binary number that ranges from 00000000 to 11111111 which is 0 to 255 decimal or 0 to FF in hexadecimal. Each digit in the binary representation of the number is known as a bit, this is short for Binary digIT. There are therefore eight bits to the byte. However as microcomputers have become more sophisticated and powerful this relatively small size of data has become something of a bottleneck to performance. Routines that needed to work on large numbers for example had to retrieve the data in small Ôbyte sized' pieces, reconstruct the number in question, act on that data, break down the result into small pieces again and send it back into the memory. Most computer languages perform all of these tasks invisibly and the user knows little or nothing about it but there was an inevitable penalty in speed. For more rapid movement and manipulation of data there has developed a need for larger data Ôpackets'. As well as the byte we now have the larger two byte equivalent, the sixteen bit word and the four byte equivalent, the thirty-two bit long word. Within HiSOFT BASIC, integers are held in 16 bits and long integers in 32 bits. These give the numbers -32768 up to 32767 for integers and -2147483648 up to 2147483647 for long integers. The reason that integers do not give 0 to 65535 is that it is generally more useful to use signed numbers rather than unsigned although some languages do give you the ability to choose between signed and unsigned integers. Using signed integer arithmetic means that, in any integer, if the most significant (the leftmost bit) is set to 1 then the number is negative. Remember, it is perfectly possible to program in HiSOFT BASIC without concerning yourself with any of these details. It is only when starting to manipulate the memory of the computer directly that they will become important. Using Logical Operators in Arithmetic We have already seen how logical operators function to enable tests to be made between two different situations. The way these actually work internally is based on comparisons between the electronic patterns of two binary numbers - bitwise comparisons. In the case of a logical test the numbers that are usually compared are -1 (or another non-zero number) and O standing for true and false. Depending on the particular type of logical test that is made, the results of the comparisons will themselves resolve to a figure that will signify either true or false and the flow of the program will be altered in response. You can also use these logical tests in expressions; the two operands will be converted to integers (or long integers) and then into binary and compared, bit by bit, according to which logical operator you are using, to obtain the result. The following table shows the outcome of the comparison of each bit for the different logical operators: 1 and 1 0 and 0 1 and 0 0 and 1 NOT 0 1 0 1 AND 1 0 0 0 OR 1 0 1 1 XOR 0 0 1 1 IMP 1 1 0 1 EQV 1 1 0 0 The type of comparisons that are made need not be confined to just true and false however, they can be used in just the same way to compare and modify two different binary numbers such as 10010011 and 11011110. They can therefore be used in the following way, for example: PRINT 162 AND 48 will be calculated by bitwise-ANDing the two binary representations of these integers as is: 162 AND 48 is 00000000 10100010 AND 00000000 00110000 _________________ 00000000 00100000 = 32 Note that we show, and calculate with, the full width (16 bits) of the binary representation of the integer - this is particularly important when using the operators NOT, EQV and IMP which may change a 0 bit to a 1 bit and thus often turn a positive number into a negative number. See if you can predict the outcome of this program before you run it : SUB L_Table(VAL x,y) REM Set tab width to 9 WIDTH 80,9 PRINT "X","Y", "X AND Y", "X OR Y", "X XOR Y", PRINT "X EQV Y", "X IMP Y", "NOT X" PRINT PRINT x, y, x AND y , x OR y, x XOR y, PRINT x EQV y, x IMP y, NOT x PRINT END SUB j=33 FOR i=4 TO 7 CALL L_Table (i,j) NEXT i These logical transformations are not of obvious use to the novice programmer but are extremely important for use in graphics work. XOR in particular has a useful effect that fairly easy to demonstrate. Consider this: 162 XOR 48 gives: 00000000 10100010 XOR 00000000 00110000 00000000 10010010 = 146 Now what about 146 XOR 48? 00000000 10010010 XOR 00000000 00110000 00000000 10100010 = 162 So, XORing a number with a constant and then XORing the result with the same constant gives us the original number. Because of this feature, XOR is used a in computer graphics to produce Ônon-destructive' animation i.e. to allow pictures to move around on a screen without permanently obliterating what was in the background. First a certain graphics pattern is XORed onto the screen memory and hence displayed. XORing the same pattern a second time restores exactly the image that was there previously. The pattern is then repositioned slightly and XORed again to give the impression of movement across the screen. The ability to XOR graphics images is implemented in the PUT statement, as shown in the following example. REM Simple use of GET and PUT DIM Amiga%(1000) ' Draw a box starting at (100, 50) LINE (100, 50) - (150, 150), 1, BF ' Get entire box GET (100, 50) - (150, 150), Amiga% CLS FOR i = 0 TO 100 STEP 5 PUT (i, i), Amiga%, XOR SLEEP PUT (i, i), Amiga%, XOR SLEEP NEXT i Type in this program and run it. Now repeatedly hit any key, as quickly as you like, to watch a black box appear and disappear while moving across the screen. You might like to save this program. Make sure you have a disk in drive DF0 with enough space on it (or room on your hard disk) and then select Save as... from the Project menu. A file selector will appear (this is the WB3 ASL file selector, yours may be slightly different): Saving a program Type in the name you want to call your program (say Amiga.bas). Now click on Save or hit Return. Strings and Things Strings, or text variables, differ from numbers in that, at least as far as the computer is concerned, there is no logical relationship between one character and the next - they are just a collection of letters and numbers Ôstrung' together. Unlike decimal numbers where each character position signifies a tenfold relationship with the one to the right, strings cannot be used in calculations. This is true even when they look like numbers. At times this distinction is very valuable - in calculating programs such as spreadsheets for example you can ask for a total of all numbers in a large block of data without worrying that the computer will also include bank account or telephone numbers as well. However there are certain manipulations we can do with strings that are impossible with numbers. Long strings can be built up from shorter ones by a process called concatenation, which really just means adding them together - try this: A$="Hello" B$="John" C$=A$+" "+B$ PRINT C$ This can be very useful for creating complex display layouts for example by making strings of tab characters - CHR$(9) - and by using carriage return codes - CHR$(13). Dividing strings into smaller portions is known as string slicing. The following functions all return strings that are at most equal to, or in some way smaller than, the original that they were given as an argument. LEFT$(string$,x) will return the first x characters counting from the left of the string. RIGHT$(string$,x) will return the first x characters from the right of the string. MID$(string$,a,x) returns a string of x characters long starting from the position a in the original (the required length, x, can be omitted in which case the entire remainder of the string is returned). MID$ can also be used to alter the data held within a string by specifying a portion that is to be replaced with a second string using the syntax: MID$(string$,a,x)=string2$ This will replace x characters from string, starting at position a, with the first x characters from string2. INSTR(a,string$,string2$) will search for the first occurrence of string2 within string starting optionally from position a. If the second string is found the function returns the position of the first character of that string. Try this: SUB Search (BYVAL A$) IF INSTR(1,A$,"HiSOFT")<>0 THEN PRINT "That's what we like to see!" ELSE PRINT "Where's the HiSOFT?" END IF END SUB DO INPUT "Gimme some words, please";Test$ Search Test$ LOOP UNTIL Test$=="END" The opposite of INSTR is RINSTR; this searches backwards through a string starting at the end. See if you can work out what this does: INPUT a$ DO i-RINSTR(a$," ") IF i=0 THEN EXIT LOOP PRINT MID$(a$,i+1); " "; a$=LEFT$(a$,i-1) LOOP These commands can be used in conjunction with each other to build up more complex string manipulation expressions. They work well in conjunction with the function: LEN(string$) that returns the length of the string in question. LSET and RSET can be used to place a string of text within a string variable of greater length such that the smaller string is assigned flush with either the left hand end (left-justified) or the right hand end (right-justified) of the larger. For these commands to work you must first create a larger string of the required size and the easiest way to do this is to use either the SPACE$ or the STRING$ commands. LSET and RSET are normally used when using files and FIELDed variables but can be used to left- and right-justify any string. A$=STRING$(30," ") B$="HELLO!" PRINT A$ PRINT B$ RSET A$=B$ PRINT A$ LSET A$=B$ PRINT A$ To illustrate the use of some of the above functions here is one that can be used as a complement to RSET and LSET to centre one text string within another. FUNCTION centre$(main$,insert$) STATIC a,b a = LEN(main$) b = LEN(insert$) IF b >= a THEN FNcentre$ = LEFTS(insert$ ,a) : EXIT FUNCTION END IF c = (a-b)\2 Ô integer division MID$(main$, c+1) = insert$ centre$ = main$ END FUNCTION DO INPUT "A word";a$ INPUT "Another word";b$ PRINT centre$(a$,b$) LOOP UNTIL a$=="END" UCASE$ and LCASE$ convert all letters within a given string into upper case or lower case respectively. These are useful routines for tidying up the response given by a user: DO INPUT "What is your name"; A$ LOOP UNTIL A$<>"" B$ = UCASE$(A$) PRINT "HELLO "; B$ Of course punctuation and numbers within the string remain unchanged. Numbers to Text and Back Again We have already touched on the subject of how letters are actually stored internally in the computer as numbers. These are known as ASCII (American Standard Code for Information Interchange) numbers and every letter or character that you can type with your Amiga will have its own internal ASCII number. Obviously it is vitally important that the computer knows at any one time whether the numbers it has stored in its memory are actually to be regarded as numbers or whether they really represent text. Say for example that you have stored on a file somewhere your telephone number 0525718181. It will be essential that your programs are aware that this is actually a text representation of a number rather than a true number. It cannot be multiplied by any other figure, divided, assigned to a numeric variable, converted to double precision and so on, and in particular it should never be used to calculate your taxable income by your home accounts program! It is to avoid such ambiguity that computer languages such as HiSOFT BASIC insist that text variables are denoted by the suffix $ and the text strings themselves are enclosed in quotes. However there are situations where it is important that the programmer has control over the way that the internal numbers are handled. It is possible to convert numeric variables to text, and vice versa, and we shall see that there are certain advantages to doing so. Text as numbers - the ASCII codes Although text is always stored in memory as ASCII numbers the computer keeps track of which variables really represent letters and need to be converted to such before displaying them on the screen. There are however circumstances where it is valuable for the programmer to be able to access these numbers directly and decide what to do with them. A typical situation may be where text data is stored in an unstructured way in memory, which could happen if it has been imported from another computer through one of the expansion ports. The actual data read and stored will be the ASCII number equivalents of that text. At a later stage if you wish to display the information on screen you will have to explicitly signal to the computer that you want the data to be interpreted as letters rather than as numbers - unlike defined variables the computer will be unable to guess what each piece of data is to represent. The keyword we use to convert a stored ASCII number to its text equivalent is CHR$(). Within the brackets we should put either a number or a numeric variable name. The computer will then convert this number to text using its internal ASCII table. The following short example will illustrate the way that the conversion of ASCII numbers to text works: a = 72 b = 69 c = 76 PRINT CHR$(a);CHR$(b);CHR$(c);CHR$(c);CHR$(79) which will produce the message HELLO. It may seem a cumbersome way of doing things, but storing text in this way can be extremely useful. In particular it is valuable when you want to print or display characters that cannot actually be typed at the keyboard. As well as producing some of the more unusual characters, different ASCII numbers can be used by your programs to trigger special effects. Data files created by word processors, text editors and the like will contain ASCII numbers. Similarly all information exchange from the keyboard to the computer, from the computer to the printer, from the computer to the disk drive and from the computer to another computer will be as ASCII information. However what happens to that information when it arrives depends on the program that is running and on the computer's operating system. For example, when ASCII codes are sent to the printer they do not all appear on paper. Some are regarded as control codes that instead trigger some sort of special effect. One such special effect code is ASCII number seven which produces a beep, either from the computer or the printer depending on where the code has been sent. Another very important control code number is ASCII 27 which is known as the escape code. Most printers use this to signal the start of some command sequence that will produce a different type style such as bold or enlarged print or some other effect. Many software programs will use their own routines to catch certain ASCII codes coming from the keyboard and to use them to trigger actions. One might do a simple test on which letter has been sent, but again ASCII codes are usually more flexible as it is possible to select unusual numbers that can only be Ôtyped' by some combination of letters such as Ctrl-X which in the Amiga keyboard produces the character number 24. Alternatively an unusual code or sequence of codes can be assigned to one of the ten function keys. One of the most common and important ASCII control codes you will ever use is number 10 which is the code for line feed. This is the code which is sent whenever you press the Return key on your keyboard and signal the end of one line and the start of the next when you are typing e.g. using your HiSOFT BASIC editor. These codes are also extremely useful for controlling the display of information on the screen. Say you want to define the text variable ADDRESS$ to produce this output: John Smith 22 Long Lane Newtown You can achieve this by using, all on one line: ADDRESS$="John Smith"+CHR$(10)+"22 Long Lane"+CHR$(10)+"Newtown" There is an associated keyword which will return the ASCII number for any given letter or text variable. This command is ASC(). Example: PRINT ASC("A") : PRINT ASC("*") Sometimes the keyword ASC() is not appropriate because it always converts the characters to their ASCII table equivalent rather than to what we would regard as their direct numeric equivalent. For example the text character 9 has the ASCII value of 57. The following line would therefore give the answer of 570. x = ASC("9") : PRINT x * 10 What we need in this case is VAL. VAL searches a string expression for anything that can be interpreted as a number, integer or floating-point. Try these out: x = VAL("9") : PRINT x * 10 PRINT VAL("180 High Street North") PRINT VAL(" -256+40=-216") a$="26.04 - the price plus VAT") PRINT VAL(a$) VAL stops looking for a number as soon as it encounters something (apart from a space, tab or end-of-line) that it thinks cannot start a number. Numbers as text Converting numbers to characters is essential whenever they are to be incorporated into a long string of text, or imported into a text data file such as one produced by a word processor etc. There are however other possible advantages in making the conversion. We have already seen how text strings can be sliced into smaller pieces, or joined together, and in several ways manipulated in a very different manner to numeric variables. There are often occasions where it is useful to be able to do the very same things to numbers. We have already seen above how to take a string and extract a number from it using VAL; the converse of this is where you want to convert a number into a text string - for this you use STR$. STR$ inserts a leading space or a minus sign, if the number is non-zero. a=1066 : b$=STR$(a)+" and all that!" PRINT b$ big=-98765.43 : b$=STR$(big) PRINT LEFT$(b$,3);",";RIGHT$(b$,LEN(b$)-3) More Ways to Store Variables and Data Computers are used all around us today, in almost every profession, business or service you can name, to store and manipulate large amounts of data. In many ways the use of variables lies at the heart of this power. For example a simple arithmetic expression can be placed inside of a logical loop and within a couple of hours it could have done literally thousands upon thousands of calculations if it was given a constant supply of numbers to work from. But where can this data come from? We obviously need a way to store and read data that is less longwinded than the simple, hard-coded system we have employed so far of variablename = data or every business program would need thousands of lines that did nothing but assign values to variables (if you could think of enough names for them). We have already mentioned disk files and these will often be the most common way of storing large amounts of variable data on your Amiga. However, for storing reasonably small amounts of data that is to be constant over the life of your program the DATA, READ and RESTORE commands are useful. The keyword DATA is used to supply a list of several items of data, each separated from the next by commas. There can be many such DATA lines within each program. The keyword READ is used to look up this data, item by item, and assign it to a named variable, or several variables in turn (with the usual proviso that string data should not be assigned to a numeric variable etc.). When reading from the list HiSOFT BASIC starts at the very first DATA line it finds. When finished it remembers its exact position in the list, ready to begin again following the next READ command. The Ôpointer' that the computer uses to keep track of its position within the list can be moved with the use of the RESTORE command. If used on its own RESTORE resets this pointer to the front of the first DATA line, but RESTORE can also be used with a specified line number or line label to move the pointer more selectively. Text string data within the list does not have to be enclosed within quotes but you will get an error if you try to read such data into a numeric variable. Try this little program: DATA "A Box" Ô quotes because of space character DATA 100,50,100,150,200,150,200,50 RESTORE READ a$ LOCATE 4,18 PRINT a$ x0=200 : y0=50 FOR a=1 to 4 READ x1,y1 LINE (x0, y0) - (x1, y1) x0=x1 : y0=y1 NEXT a Another system for storing large amounts of information is the use of dimensioned variables. These allow information to be held in a much more dynamic way than is possible with DATA statements, but make the organisation and management of the data much easier than with simple variables. The keyword used to produce a dimensioned variable is DIM. A dimensioned variable allows several items of information to be accessed under just one variable name by specifying a numeric position for the data within a list. Take this example: DIM x(12) This dimensions the variable x to hold 13 entries of data each accessed in turn as x(0), x(1), x(2), x(3), x(4), x(5), x(6), x(7), x(8), x(9), x(10), x(11) and x(12). This list of variables, all stored under the same name, is known as an array. Data is placed in each of the entries of a dimensioned variable in just the same way as with a normal variable e.g. DIM A$(21) a$(12) = "Fred Smith" It is also possible to define a two dimensional array as in DIM x(9,9) which will store 10*10=100 items of integer data. The data in a two dimensional array can be accessed as if part of a table, with each data item read by specifying the Ôrow' and Ôcolumn' number. Similarly a three dimensioned array can be made which can be looked on as perhaps many different tables held on several pages of a book. Three dimensions is the limit to what we can visualise in terms of everyday objects but the number of dimensions you can have in an array is unlimited. Arrays of this form can be used to store data in a highly structured way. For example a series of string variables used by an address book program can be subdivided to store names, addresses, home telephone numbers, work address, work telephone number etc. and chosen parts of the data can be called up by specifying the appropriate array position. It is not always necessary to use the DIM command when using dimensioned variables. Any variable name that is given an array position when it is first used will automatically trigger the formation of a dimensioned array of that name as in: X(2)=20 PRINT X(1) However when this system is used it creates a default array size of just 11 elements in each dimension referred to in the variable name. HiSOFT BASIC provides a range of error checks on arrays which are controlled via the compiler options requesters. With Array checks on you will be told at runtime if you access an array element beyond the dimensions of the array. When you are sure that your program is safe from such errors you may want to turn Array checks off to increase the speed of the program and decrease its size. However, be warned; if you are relying on the auto-dimensioning of small arrays described above, it is a good idea to turn Array check warnings on before you declare your code finished and turn checks off. This will tell you if you have accidentally used an array without dimensioning it first. If you run a program which uses an un-DIMed array with Array checks off, a program crash may result. The SHARED keyword can be used in conjunction with DIM to allow the array data to be shared between the main program and sub-programs without the need for a SHARED statement in each sub-program. Thus DIM SHARED A$(20) in the main program will allow the 21-element array A$ to be accessed by all sub-programs. Many people find the dimensioning system confusing in that an array of say x(10) actually stores eleven items of data. This is because it includes x(0) as a legal data position. The command OPTION BASE 1 forces the lowest entry in an array to be position 1, in which case DIM x(10) would only allow ten items to be stored. You can reset the default situation with the command OPTION BASE 0 but there are no other options allowed. REDIM is a command that allows the size of the dimensions of an array to be re-allocated. For example DIM x(9,19) can be REDIMensioned to the new layout of x(9,39) by: REDIM x(9,39) Ô all data in x() is lost Use this command with care as the data that is held in the array will be lost. REDIM PRESERVE allows one dimensional arrays to be enlarged or truncated whilst retaining the data in the un-altered array elements. When an array no longer is to be used it can be ERASEd, which clears all entries and reclaims the memory allocated. When writing sub-programs the situation often arises where the programmer can anticipate that the sub-program will be passed a variable array as a parameter, but cannot anticipate the likely size of this array. Similarly you may wish to write program routines that can work on an array that may constantly be REDIMensioned. HiSOFT BASIC has two functions which allow the size of a given array to be tested. UBOUND(name,x) returns the highest allowable entry of the Ôxth' dimension of the array allocated to the variable name. LBOUND(name,x) will do the same for the lowest allowable entry number, which will be either 0 or 1 depending on the setting of the OPTION BASE command before the array was first dimensioned. Finally there is a command that can be used with any pair of variables but is particularly useful for manipulating and moving around the data held in variable arrays. SWAP var1,var2 will exchange the values held in the two variables, providing they are both numeric variables of the same type, or both string variables. Below is a complete example using these new array handling keywords: DEFINT a-z SUB Shell_Sort(Words$(1)) STATIC HowFAR, Top, i, j Top=UBOUND(Words$) HowFar=Top\2 DO WHILE HowFar>0 FOR i=HowFar TO Top-1 j=i-HowFar+1 FOR j=(i-HowFar+1) TO 1 STEP -HowFar IF Words$(j)<=Words$(j+HowFar) THEN EXIT FOR END IF SWAP Words$(j), Words$(j+HowFar) NEXT j NEXT i HowFar=HowFar\2 LOOP END SUB Type this shell sort program into HiSOFT BASIC and then devise a means of filling an array with some words and calling Shell_Sort to sort them into alphabetical order. Closing Down STOP, END and SYSTEM are all commands that produce the effect of stopping the current program, closing all files and returning control to the calling operating system. They can be used as options within an IF...THEN branch or similar for bringing an end to the program. If the current program that is being executed ever Ôruns out of lines' the effect is similar to placing an END command at the final line. SYSTEM differs from END and STOP in that it suppresses the Press any key message that normally appears when a HiSOFT BASIC program finishes. The Screen Producing output on the screen is at once one of the simplest and one of the most complex tasks possible. When you type PRINT "Hello" this single command triggers an enormously complex series of routines that produce exactly the correct pattern of dots on the screen, in exactly the correct place amongst the thousands of dots that there are, to create the image of the word Hello. Normally all of these processes are controlled automatically by the operating system of your computer. This makes things very much simpler for you, but also inevitably limits the options available for tailoring the display. On the other hand the Amiga has some of the most powerful and flexible graphics abilities yet seen on a microcomputer. To exploit these, HiSOFT BASIC allows a great deal of control over various aspects of the Amiga's operating system, including Intuition. These are explained in the Chapter 9 : Operating system Support and the following section will only cover the commands briefly to put their usage into context. A Word About Intuition The Amiga range has an unusually sophisticated system of screen handling through the Intuition graphics environment. Intuition allows the user to control the screen display via multiple screens, multiple windows, graphic icons and many other display features and options. These can in turn be linked to, and controlled by, the keyboard and mouse input devices. Obviously, HiSOFT BASIC has to be able to let the user access the power of the operating system from within their own programs. However it would be completely impractical and unwieldy to provide special keywords for each of these functions, and their possible permutations. HiSOFT BASIC gives you a powerful and flexible interface with all the Amiga operating system calls by supplying a direct link via include files derived directly from the standard Commodore include files used by Assembler and C programmers. We will briefly describe the use of the include files here and give an extended example in the next section. Once the include file is installed and accessible to your own programs you will be able to call any of these procedures by name, as if they were keywords, and pass the required parameters to them to get the result you want. There are one or two pieces of bad news. Firstly, some of the operating system calls are detailed and fairly complicated and there is not sufficient room in this book to give details of what each one does. Operating System Support (Chapter 9) is a reference to all the various files provided, but if you intend to take Amiga programming more seriously, you will almost certainly have to buy copies of the Amiga ROM Kernel Manuals (even though these are targeted at C programmers), never-the-less they are the final word on using the Amiga operating system. Text on Screen When placing text on the screen, there are really two broad classes of commands - those that position the text on the screen, and those that place the text at that position, and control the way that it looks. The position where text will appear as soon as a PRINT or similar command is given is known as the text cursor. The screen of the computer is regarded as being divided into small squares, each big enough to hold one letter, of which there are eighty across and twenty down on a regular high resolution screen. The current horizontal position of this cursor can be determined by using the function POS, the vertical position by the function CSRLIN. The text cursor can be re-located at any of these text squares by the command LOCATE x,y where x is the vertical position and y the horizontal. Either x or y can be omitted if their current values are to remain unchanged (although the commas that separate them must remain if x is omitted). One other optional parameter makes the cursor invisible or visible - see the Command Reference. Try: LOCATE 10,10 PRINT "Jim" LOCATE 9,10 PRINT "Hello" Which will produce the output: Hello Jim Cursor positioning can also be achieved by typing the two Ôinvisible characters' - spaces and tabs. The keyword SPC(x) can be inserted in any PRINT statement and it will cause the cursor to skip x spaces before the specified text is displayed. TAB(x) used in a similar situation will cause the text cursor to jump to column number x (if x is less than the current position, the cursor will move down by one screen line first). Note also the use of STRING$ and SPACE$ which can be used in PRINT statements. PRINT SPC(10), "Hello" PRINT TAB(10), "Hello" will both produce the result: Hello as will: Let A$=SPACE$(10) PRINT A$+"Hello" Note that the command TAB(x) does not really print a tab character but causes the cursor to jump to the required spot on the screen. The true tab character is designated by CHR$(9) which can be used by many printers, and some programs such as word processors, to trigger a jump to preset tab positions. This is the character usually returned by the tab key on the keyboard. With the cursor positioned where it is required, text can be displayed by the PRINT command. As we have seen even this has its own parameters that control the subsequent positioning of output - if text or a string is terminated by a semi-colon future output begins adjacent to that output. If it is terminated with a comma the next item to be printed starts at the next available x-column position on the screen. The default tab width is 14 and this can be changed using the WIDTH command. If text reaches the end of a line it automatically wraps around to the start of the next. However it is possible to define a smaller maximum line size by the use of the WIDTH command. WIDTH 20 PRINT "Hello Hello Hello Hello Hello" will produce: Hello Hello Hello He llo Hello Normally when the PRINT command is asked to display data, such as a double precision number, it shows it on screen in a way that reflects its internal format. Often, however, it is not desirable to have too many decimal places displayed, or we may wish to manipulate the display in some other way e.g. to add currency symbols to the front of the data. The PRINT USING command does all this and more and is covered in detail in the Command Reference section. The WRITE keyword is also used to place data on the screen but it differs from PRINT in that it does not respond to any formatting commands. Try this example: REM An example of using WRITE a=42 : b=a MOD 5 : c#=22/7 WRITE "The answer is "; a WRITE b*5, c# Run the program - is that what you expected? Any strings that are printed will be enclosed in quotes. In practice WRITE is more useful when debugging, or when producing disc files where the internal format of the number needs to be preserved. PRINT is of more value for the screen display in most other cases because of its extensive formatting control. Finally, an important command for controlling the text, and indeed graphics, display is CLS which means Ôclear screen'. This command will also return the cursor to the top left hand edge of the screen ready for printing new data. CLI vs. Workbench - when is text not text The Amiga comes supplied with a standard character set which gives the only characters available for printing text in CLI mode. One of the main features of Workbench (and the whole of the graphical aspect of the Amiga operating system), on the other hand, is that it is possible to use several different fonts and text styles, such as italic or bold print, on screen. These special font styles cannot be handled in the same way as normal text - they are in effect Ôpictures of text' that are drawn on the screen. The ordinary HiSOFT BASIC commands for displaying and manipulating text are not be capable of manipulating them in the required way, and instead your program will have to pass commands through the OS interface header files. Graphics Two ways to produce graphics There is more than one way to produce a picture on your screen, but to appreciate the difference between the various techniques involves an understanding of how the screen display memory works. The image you see on the screen is made up of a matrix of small dots known as pixels - in the high resolution Amiga monochrome mode there are (at least) 640 * 200 pixels which makes 128000 dots in total. As with any form of data, if the computer is to remember the image that is to be retained on the screen it needs to put aside a certain amount of memory for the task. It is possible for sections of the screen to be accessed directly, altered, moved, copied or saved to disk. Say that we have a screen with a simple graphic image on it - one straight line. The program that draws that line may be only one command long and can be saved in a disk file that is less than 1K in size. Every time we wish to create that graphic image again the program can almost instantly be run. However if we wish to save the entire memory image of that screen the file created will be many times as large, and will probably still be loading long after the first version has finished. Alternatively a detailed graphics image, say a landscape scene, may require an complex program of many hundreds of lines to re-create it. If the screen picture is a digitised image of a video picture or a creation from a computer art package it may be practically impossible to write a program that could reproduce it. In this case a simple file that stores the pixel data for the screen image will be the most memory efficient and speedy system to use. HiSOFT BASIC's graphics keywords contain options that will make it easy for you to choose whichever of the above approaches best suit your needs, or to mix them at will. As with text the following keywords require a means of signalling to the computer exactly where you want the graphics to appear as well as what graphic image it is to print. The current position where graphics will appear is known as the graphics cursor. The following commands all produce certain simple shapes on the screen at specified positions. Their full range of permutations and options are detailed in the Command Reference chapter. Movements of the graphics cursor can be expressed in absolute coordinate terms like that used to move the text cursor (although of course the range of coordinates available changes depending on the current screen resolution). Alternatively it is sometimes possible to express the required movement relative to the current graphics cursor position using STEP. LINE is used to draw a line or a box on the screen in a chosen line style and colour. CIRCLE does the same for any size of arc, circle or ellipse. LINE (100, 100) - (200, 100) will draw a line from the pixel position (100,100) to the pixel position (200,100). LINE and PAINT are used to draw shapes that are filled by a specified colour or tile pattern. The ability to rapidly and automatically fill shapes with a given pattern, which can be extremely complex, is one of the most powerful features of the Amiga and can give a very professional look to your programs if used with restraint. PSET and PRESET both draw a pixel dot at the specified position on the screen in the specified colour. If the colour option is omitted PSET will default to the current foreground colour but PRESET will default to the background colour. POINT is a complementary command that will read the colour displayed at specific pixel position. Using Colours The standard Amiga A500 computer is capable of displaying a maximum of thirty-two colours at once (without using EHB or HAM modes). The current colours for printing and drawing can be selected by the programmer from a list of 0-31, detailed in the Command Reference section, under COLOR. The extra palette and colours of the Amiga 1200 and 4000 can be accessed from HiSOFT BASIC in two ways: via the built-in PALETTE statement and via the Graphics library LoadRGB32 and SetRGB32 functions. COLOR (with apologies for the American spelling) can be used to choose the colour with which text is printed and the colour of the background, or Ôpaper', on which it is displayed. When programming in CLI mode these are the only options available. PALETTE allows the programmer to change the actual colours displayed on the screen. In this way the displayed colours can be changed even when, say, in the standard Workbench mode which uses a screen display with only 4 colours (0 - 3). Try the following program: REM $NOWINDOW Ôinhibit the standard BASIC window DEFINT a-z REM $INCLUDE Graphics.bh ÔUse Graphics for WaitTOF LIBRARY OPEN "graphics.library" Ô Open a screen for our window SCREEN 1,,,2,2 Ô4 color high-res non-interlaced screen WINDOW 1,,,511,1 Ôa "Works-Burger" window (everything on it!) ' Draw three boxes in a triangular formation FOR i = 1 TO 3 x = 270 Ô set up co-ordinates for each box y = 25 Ô depends on the screen resolution! IF i = 2 THEN x = x - 75 : y = y + 65 IF i = 3 THEN x = x + 75 : y = y + 65 LINE (x, y) - STEP (100, 50), i, BF NEXT i ' Cycle through all the colours FOR red! = 0.0 TO 1.0 STEP 0.03125 FOR green! = 0.0 TO 1.0 STEP 0.03125 FOR blue! = 0.0 TO 1.0 STEP 0.03125 Ô initialise each of the three colours we use PALETTE 1, red!, green!, blue! PALETTE 2, blue!, red!, green! PALETTE 3, green!, blue!, red! WaitTOF WaitTOF NEXT blue! NEXT green! NEXT red! A colour that is altered by the PALETTE command changes instantly on the display without anything having to be redrawn. Many forms of graphics effects and computer animation are possible as a result. For instance two different colour numbers can be set by the PALETTE command to actually display the same result on the screen. An image drawn in one colour will therefore be invisible against a background using the other. A new PALETTE command that sets the image colour number to a different setting will make that image appear instantaneously. REM $INCLUDE Graphics.bh This compiler meta-command includes all the definitions, functions and sub-programs available in the standard Amiga graphics library. We do this to gain access to the WaitTOF sub-program which waits for the Amiga to indicate that the video-beam on the monitor has returned to the top of the display. This allows us to animate the colours on the display without them flickering (caused by changing the colour whilst the beam was passing the point at which we changed it). Pre-tokenised files The next program we consider is going to use the Amiga's graphics library without using the BASIC commands. It needs to include both the Exec.bh and Graphics.bh files. When you are developing such a program its a bit boring waiting for the compiler to tokenise all the constants in the include files every time you make a change to your program; after all , these identifiers are going to be the same each time. HiSOFT BASIC 2 enables you to pre-tokenise these and then load the pre-tokenised information when you compile your main program. For the technical details of pre-tokenisation see the end of the Compiler Section but here's how to use it in practice: First create a file with just the includes that we want like this REM $INCLUDE Exec.bh REM $INCLUDE Graphics.bh and save this out as abc_inc.bas using the Save command from the Project menu. Select Tokenise from the Program menu and the pre-tokenised file abc_inc.t will be produced. To use this file select File sub-item from the Compiler item on the Settings menu: T how click on Set by Tokens file and select the abc_inc.t file with the file requester. Now if you compile a program that uses these include files, it will compile much more quickly. Note that you will need to remove, or comment out, the corresponding REM $INCLUDE lines in your main program otherwise you'll get two copies! When you have finished using a pre-tokenised file you can select another one or select the Token File line and use AX to remove an existing one. Example Graphics Library Program Here's a program that uses the Amiga's graphics library without using the BASIC commands. This program should help you to understand the principles of using the Amiga libraries. DEFINT a-z ÔREM $INCLUDE Exec.bh ÔREM $INCLUDE Graphics.bh LIBRARY OPEN "graphics.library" CONST length% = 40, skew% = 15 CONST start% = 260, ystart% = 110 fb$ = "HiSOFT BASIC 2" ' 'SafeSetOutlinePen `a backwards (= 39 THEN junk& = SetOutlinePen&(w&, c) ELSE POKEB w& + AOlPen, c POKEW w& + RastPortFlags, PEEKW(w& + RastPortFlags) OR AREAOUTLINE& END IF END SUB REM Sub-program to draw a 3-D box with a letter SUB draw_box(BYVAL rp&, BYVAL x, BYVAL y, ch$) STATIC ch_x, ch_y, junk& REM Draw outline of a box SetAPen rp&, 0 Ô set foreground pen to background color junk& = AreaMove(rp&, x, y) junk& = AreaDraw(rp&, x + skew, y - skew) junk& = AreaDraw(rp&, x + length + skew, y - skew) junk& = AreaDraw(rp&, x + length + skew, y + length - skew) junk& = AreaDraw(rp&, x + length, y + length) junk& = AreaDraw(rp&, x, y + length) junk& = AreaEnd(rp&) REM Now draw 3 lines to complete 3-D box SetAPen rp&, 1 Ô select standard pen Move rp&, x, y Draw rp&, x + length, y Draw rp&, x + length + skew, y - skew Move rp&, x + length, y + length Draw rp&, x + length, y REM Draw the letter text, centred in the box ch_x = TextLength&(rp&, SADD(ch$), LEN(ch$)) ch_y = PEEKW(rp& + TxHeight) Move rp&, x + (length - ch_x) / 2, y + (length + ch_y) / 2 Text rp&, SADD(ch$), LEN(ch$) END SUB REM The main program 'force the BASIC runtime library to initialise TmpRas and ÔAreaInfo in the default window's RastPort (!) AREAFILL SafeSetOutlinePen WINDOW(8), 1 Ôset the outline pen colour draw_box WINDOW(8), xstart, ystart, "B" draw_box WINDOW(8), xstart + 7 * length / 6, ystart, "C" draw_box WINDOW(8), xstart + 7 * length / 12, ystart - 7 * length / 6, "A" fb_width = TextLength&(WINDOW(8), SADD(fb$), LEN(fb$)) fb_height = PEEKW(WINDOW(8) + TxHeight) fb_x = xstart + (13 * length \ 6 + skew - fb_width) \ 2 fb_y = ystart + length + fb_height * 3 \ 2 REM Display picture title Move WINDOW(8), fb_x, fb_y Text WINDOW(8), SADD(fb$), LEN(fb$) END Daunting? Not really ... let's take it apart. First of all, type it in, once it is running successfully it should produce the following output: As simple as A B C! Now let's see how it's done ... First of all, what are we trying to draw? Answer: three 3-dimensional boxes, with letters inside them and a title below all this. How are we going to do the boxes? Look at the following diagram: Figure 3.1 The problem ... We have to draw 3 boxes, all the same (except for the letter inside it), so it makes sense to have a sub-program to draw one box. To make a box, we could just draw 9 lines but there are several graphics library call to help us out ... The graphics library functions AreaMove, AreaDraw and AreaEnd are used to define the vertices of the polygon we want to fill. Look at the box in Figure 3.1, formed by points 1, 2, 3, 4, 5, and 6 - this is a closed polygon and we could therefore draw it with AreaDraw. What are the points? Well, if point 1 is (x,y) and the length of each side of the box is length and the depth, measured vertically and horizontally, is skew then the points are as follows: Point 1 : (x, y) Point 2 : (x+skew, y-skew) Point 3 : (x+length+skew, y-skew) Point 4 : (x+length+skew, y+length-skew) Point 5 : (x+length, y+length) Point 6 : (x, y+length) So, to draw this polygon we must set up the 6 points (we AreaMove to the first point, AreaDraw to the next five, then mark that we're done with AreaEnd): REM Draw outline of a box junk& = AreaMove(rp&, x, y) junk& = AreaDraw(rp&, x + skew, y - skew) junk& = AreaDraw(rp&, x + length + skew, y - skew) junk& = AreaDraw(rp&, x + length + skew, y + length - skew) junk& = AreaDraw(rp&, x + length, y + length) junk& = AreaDraw(rp&, x, y + length) junk& = AreaEnd(rp&) To draw this polygon we should first set up the colour index of the fill and the border; this is done with SetAPen (for the fill) and the curious SafeSetOutlinePen sub program (which sets the outline colour, which we'll investigate later, but ignore for the moment...). SetAPen simply sets the Amiga's foreground pen (which is historically known as the A-Pen. The Amiga has a second pen, the background pen, which is known as the B-Pen - not surprisingly the sub-program SetBPen is used to set it! Historically the outline pen is also known as the O-Pen. In order to call the SetAPen sub-program we also need a ÔRastPort pointer'; the internal details of this aren't important to us at the moment, but a quick look in the Command Reference tells us that the WINDOW(8) function gives us a pointer to the RastPort for the current window. Internally the Amiga graphics library is capable of drawing into many windows at the same time (since the Amiga is a multitasking machine), so for each of these drawing operations it needs a drawing context; the RastPort is this context information (it contains the current A pen and B pen settings for example). A RastPort is associated with every window in the system, every screen, and any other RastPorts which the programmer creates. Now, drawing the polygon hasn't given us our 3D box yet - we need to add lines from point 7 to point 1, from point 7 to point 3 and from point 7 to point 5 (see Figure 3.1). We need another graphics library call to do this (we could use the BASIC LINE command but since we're trying to do this without using the BASIC routines... the two calls which we need are Move (to move the drawing position), and Draw (draw a line from the current drawing position). REM Now draw 3 lines to complete 3-D box SetAPen rp&, 1 Ô select standard pen Move rp&, x, y Draw rp&, x + length, y Draw rp&, x + length + skew, y - skew Move rp&, x + length, y + length Draw rp&, x + length, y Again we need to select the correct pen for drawing, so SetAPen is again used to select the pen we need (pen 1 this time). The pen numbers used on the Amiga from Workbench 2.0 onwards are not strictly fixed (as we are using them), the correct procedure is to call the OS routine GetScreenDrawInfo which returns a complete description of the user's preferred graphic context (as set by the various Preferences editors), and then extract the desired information; for simplicity this has been skipped here. So, we've drawn a 3D box; now we have to put a character in it. To do this we use the graphics library Text sub-program; this draws a character at the current graphic location (as set by Move). We also need a bit of arithmetic to work out where to place this character so that it appears in the middle of our box. First we need to know the width and height of a character - can the graphics library help? There's a routine called TextLength that returns the number of pixels needed to enclose a text string horizontally: ch_x = TextLength&(rp&, SADD(ch$), LEN(ch$)) The TextLength function takes three parameters: rp& - the now familiar RastPort pointer, a pointer to the start of the string ch$ (for which we use the SADD function, this converts a BASIC string into a pointer suitable for the operating system), and LEN(ch$) - the length of the string. Having found the number of pixels across our text will be, we now need to find its height; this is stored in the RastPort pointed to by rp&. To find the value, we have to PEEK at the word in the RastPort structure at the TxHeight offset, hence: ch_y = PEEKW(rp& + TxHeight) So now we have ch_y as the height of the string and ch_x as the width. To put the string in the centre of the front face of the box (the face is length wide by length high, remember), we need to put the bottom left of the character at co-ordinate (x+length/2-ch_x/2, y+length/2+ch_y/2) where (x,y) is the top left of the front face. Look at Figure 3.1 to see that this is right. Keep looking! So, this should do it: Move rp&, x + (length - ch_x)/2, y + (length + ch_y)/2 Then having moved to the correct co-ordinates we actually have to draw the text: Text rp&, SADD(ch$), LEN(ch$) Notice that the parameters passed to the Text sub-program are identical to those passed to TextLength. That effectively completes the draw_box sub-program. Notice that we have passed the RastPort (rp&), the top left (x, y) co-ordinates as parameters and also the letter that is to be contained in the box is a parameter (the third one). Now we've completed the draw_box sub-program we need to write our main program, obviously the main thing for this to do is to draw the three boxes and the title, however first we need to do some extremely important setup: Ôforce the BASIC runtime library to initialise TmpRas and ÔAreaInfo in the default window's RastPort (!) AREAFILL The AREAFILL command is a standard BASIC command which is normally used for doing very much the same sort of thing as we did with AreaMove et al. However when used in this special way (with no parameters) on the default window it forces several internal things to be set up in the RastPort which are not normally set up until a fill occurs (to save memory). Since we're not using the BASIC fill commands we must ask BASIC to set up these things in advance for us. The elements within the RastPort which must be set are the TmpRas (a second, temporary, RastPort) and an AreaInfo array. The first of these is used whilst the graphics library is rendering the resulting polygon, the latter is used for storing the vertices of the polygon until the AreaEnd sub-program is called (BASIC normally initialises the AreaInfo with enough space for 20 vertices - if you exceed this any additional vertices will be ignored). The other piece of set up which we ignored until now was setting up the colour of the outline used for the polygons. To do this we need a bit of chicanery involving the RastPort, together with some graphics library version dependent code: SUB SafeSetOutlinePen(BYVAL w&, BYVAL c) STATIC junk& IF PEEKW(LIBRARY("graphics.library") + lib_Version) _ >= 39 THEN junk& = SetOutlinePen&(w&, c) ELSE POKEB w& + AOlPen, c POKEW w& + RastPortFlags, _ PEEKW(w& + RastPortFlags) OR AREAOUTLINE& END IF END SUB ... SafeSetOutlinePen WINDOW(8), 1 Ôset the outline pen color The first line checks the version of the graphics library which is in use, the function LIBRARY("graphics.library") finds the location of the library base pointer, to this we add the offset to the version number word (lib_Version) and PEEK the word at the location. In this case we need a graphics library version after V39 (Workbench 3); in this version an additional function, SetOutlinePen was added which sets the outline pen for us. IF PEEKW(LIBRARY("graphics.library")+lib_Version)>=39 THEN If we find we don't have a new enough graphics library, then we fall back to setting the outline pen in a manner which works on older versions of the operating system: POKEB w& + AOlPen, c POKEW w& + RastPortFlags, _ PEEKW(w& + RastPortFlags) OR AREAOUTLINE& Here we first set the outline pen by POKEing the byte at the AOlPen (Area Outline Pen) offset in the RastPort (w&), then we need to tell the graphics library that it should render the pen when drawing the areas (as opposed to not drawing an outline around the polygon at all). To do this we have to set the AREAOUTLINE flag in the RastPort's Flags; first we PEEK the existing value (since we don't want to disturb any of the current bits which are set), OR in the value of the AREAOUTLINE flag, then POKE the result back. Finally we call the SafeSetOutlinePen sub-program in the main body of our program to set the pen, as you can see the sub-program nicely hides all of the complexity of setting the outline pen correctly on all OS versions, making for a clearer, easier-to-understand program. Now that we've (finally!) completed all our setup, all that remains is to call draw_box 3 times (for 3 boxes) and then to write some text underneath the whole thing. Again, we'll use Text to write the message and do a little arithmetic to make sure that the text message is centred under the boxes: draw_box WINDOW(8), xstart, ystart, "B" draw_box WINDOW(8), xstart + 7 * length / 6, ystart, "C" draw_box WINDOW(8), xstart + 7 * length / 12, ystart - 7 * length / 6, "A" fb_width = TextLength&(WINDOW(8), SADD(fb$), LEN(fb$)) fb_height = PEEKW(WINDOW(8) + TxHeight) fb_x = xstart + (13 * length \ 6 + skew - fb_width) \ 2 fb_y = ystart + length + fb_height * 3 \ 2 We hope that has given you some insight into using the Amiga libraries - it is really not as daunting as it first looks and well worth the initial effort! Of course, if you find the library functions and sub-programs difficult to remember or clumsy to use you can always re-write and re-name them. When using the Amiga's operating system directly it is essential to have documentation on the various calls; this is all contained in the ROM Kernel Manuals (RKMs). Although these are written for C and Assembler programmers, learning a little C is enough understand much of what the RKMs cover. Without the RKMs programming the Amiga at the operating system level is almost impossible. Blitting There is an alternative approach to producing graphics effects which relies on using keywords which directly access and manipulate areas of the screen display memory. Moving or copying such areas of memory, or switching one area of screen memory with another, can be used to produce outstanding animation effects. Moving bits of screen memory around is known as blitting (the word stems from bit block transfer). GET and PUT allow the program to pick up a specified rectangular block from the screen display and place it in a different position on the screen. An array of the appropriate size has to be specified for storing the data. A block that is picked up by GET is not automatically obliterated from the screen but merely copied into an array. Several arrays can be filled using the GET command. These can also be copied to a sequential disk file in the same way that any array would be, ready for subsequent re-loading. In this way you could prepare a program that contained several digitised images, for example, that can be used for illustrations, or could be subsequently PUT into the same position to produce an animated effect. In the past these techniques have been felt to consume too much memory to allow their frequent use in micro-computer programs, but most Amiga machines have lots of RAM to spare and can hold many such arrays at once, leaving you free to produce incredibly sophisticated effects with ease. The PUT command has several options that allow different graphics and animation effects to be achieved using the techniques described in Using Logical Operators in Arithmetic where an example of GET and PUT was also given. These options are detailed in the Command Reference section. Trial and error is also a good way of seeing how these routines work in practice - experiment with PUTting picture blocks onto a blank screen and onto a screen that already contains a detailed image to see how different effects can be achieved (people using a colour display will find a lot more interesting effects are possible than with a monochrome monitor). Windows Anyone who has had experience of using the Amiga will quickly learn what windows are, what they can do and how they can give your programs a professional and polished air. Many languages will only allow you to use windows by making rather complicated calls to the Intuition system. HiSOFT BASIC however comes with a comprehensive selection of built-in commands that are designed to make the process of calling windows as easy and as fluent as possible. The built-in commands are fully documented under the WINDOW keyword, in the Command Reference section and we will give an example here. WINDOW is a remarkably compact keyword requiring you only to provide several parameters such as the size, position, nature and contents of the window and HiSOFT BASIC and Intuition do all of the work of drawing the image on screen etc. Note that when using multiple windows each is referred to by a specific identification number in a way that parallels those required for managing several disk files at once. DEFINT A-Z ' get the width & height of the free BASIC window ww = WINDOW(2) wh = WINDOW(3) ' draw an ellipse using pen 1 in the free BASIC window CIRCLE (ww \ 2, wh \ 5), ww \ 3, 1,,, (wh / ww / 2) ' fill from the centre using pen 3, outwards to pen 1 PAINT (ww \ 2, wh \ 5), 3, 1 ' open another window, id number 2 WINDOW 2, "Another window", (ww \ 4, wh \ 2) - (ww * 3 \ 4, wh) 'Print something novel in window 2 PRINT "Welcome to HiSOFT BASIC 2!" When you run the above program, it should look like this: Window handling in HiSOFT BASIC Talking to the Outside World It is probably true to say that taking in input and producing output, are the most important jobs that a computer program has to do. There is very little value to line after line of complex calculation or data manipulation unless at the end of it all an answer is displayed on a page or the screen. We have already seen that without a PRINT command most programs are functionally useless. Similarly it is certainly true that, with the exception of some graphics demos and the likes, most programs are useless unless they take some information from the user, a keypress or menu choice at the very least, or from the outside world via equipment such as a temperature sensor. We have already looked at what are perhaps the two most fundamental commands that deal with input and output of information, INPUT and PRINT. There are however many different options that exist to complement or modify these basic keywords. Input and Output There are several possible sources of information, not just data but also commands from the user, that are available for your program to act upon - these include the keyboard and mouse, files held on disk, and the computer's hardware ports. The options available for output include again the hardware ports, disk files, the speaker, a printer and, of course, the screen. The operating system of the Amiga computer itself can also often be looked upon as an external source of information for your programs as it acts as an intermediate between BASIC and the hardware. The operating system is a special master program that is in charge of everything the computer does. It recognises what key you are pressing on your keyboard, it knows how to make the disk drives spin round when required and so on. It is this master program that also contains all of the graphics routines. The operating system keeps charge of the date and time clock that is available through commands such as DATE$, it is the operating system that provides information on the status of the disk drive (such as which files are present), it is the operating system that detects when many hardware devices signal an error (such as disk missing from the drive). All of these items of information can be used by your own programs to control which command lines and subroutines are to be executed. Similarly when outputting data or instructions, the operating system may be the immediate receiver of the information you send. For instance when sending graphics commands your programs will be talking to the graphics library to get the desired effects rather than attempting to manipulate the screen display directly. Each of these input/output Ôtargets' can be accessed by special related keywords or pre-defined library routines. The Keyboard, Joystick and Mouse We have already looked at how the INPUT command works but although this is really a very simple keyword there are a few important options available. You have the ability to suppress the question mark the command displays by the use of a comma instead of a semi-colon after the keyword. The response given by the user must of course match the type of variable expected by the INPUT keyword or the user will get the message Redo from start. This is a wonderfully ambiguous message and means Ôredo the current entry from the start' (as if you could do anything else) rather than Ôredo every input question you may have just been asked'. Strings entered as input need not be placed within quotes, and as long as they are not so enclosed the quote character - " - can be used as part of the string (but obviously not in the first character position). Several items of data can be requested and entered in one go, although the number and type of the data entries must again match those required. In both the program line and during the entry process all items must be separated by commas. LINE INPUT will read an entire line of data, until terminated by a carriage return, and assigns it to one string variable. It does this even when the information is separated into sections by commas which would have been taken to be data separators by the INPUT command. If you have a line: INPUT A$, B$, C$ and the user enters: Bob Smith, 10 New Road, London then A$ will equal Bob Smith Whereas if you had a line: LINE INPUT A$ which is given the same input A$ would be given the value of: Bob Smith, 10 New Road, London LINE INPUT is particularly useful when reading an ASCII file from disk, e.g. a file created by the HiSOFT BASIC editor. Each line can be extracted from the file in turn and displayed, printed, or both regardless of whether it contains commas or any other form of punctuation. Both INPUT and LINE INPUT allow the user to edit the data while it is being entered to make corrections by using the Backspace key to delete data already entered. A closely related command is INKEY$ which reads the keyboard to see if any key, or combination of keys, has been pressed. If no key has been pressed an empty or null string is returned. The important differences from INPUT are that only one character value is returned at a time, and that the program does not have to wait for the user to hit Return before it can start to respond to the key. If a key is pressed which is defined to produce a string of text, such as one of the function keys, INKEY$ will only take one character from the string every time the command is made. INKEY$ is often used in conjunction with logical loops such as DO...LOOP to wait for a response from the user e.g. SUB Wait PRINT "Press any key to continue" DO LOOP UNTIL INKEY$<>"" END SUB Note that this is an example of particularly bad Amiga programming! Because of the way we've used INKEY$ the computer is being forced to "busy wait", i.e. we're keeping it tied up doing nothing which means that other programs can't have as much time to execute. A much better method is to use the SLEEP statement which puts your program to sleep until an event of some form arrives. The INPUT$(x) command is like a hybrid between INPUT and INKEY$ - it reads x number of keypresses from the keyboard and passes them directly to the program. This command is particularly useful as it allows a long string to be entered from the keyboard which need not be terminated by Return, and which can even contain, CHR$(13), in the middle somewhere. Control of the mouse is via the keyword MOUSE This is very simple to use and there is a good example of its use in the Command Reference section. The various joystick ports can be read by the following keywords. STICK(n) is a function that returns a reading of the position of a joystick. The information the program requires from this function is signalled by the value of n which is passed. This can be 0 - 3 and the values returned respectively are the x position of joystick 1, the y position of joystick 1, the x position of joystick 2 and the y position of joystick 2. The values returned are -1 (Left/Up), 0 (Centre) and 1 (Right/Down). STRIG(n) returns a value as to whether any of the joystick buttons has been pressed; n can be from 0 to 3. Respectively these signal whether the button on joystick 1 has been pressed since the last STRIG(0) command, whether button is currently pressed, whether button on joystick 2 has been pressed since the last STRIG(2) command, whether button is currently pressed. The Printer The printer is not a standard peripheral for the Amiga computer, but it is so common for people to buy one that it is given full support from the operating system and from within HiSOFT BASIC. The commands used for producing output on the printer are in many ways identical to those for laying out text on the screen, with the obvious exceptions that it is usually only possible to move the printhead down the page (some printers have a limited reverse paper feed option but it is rarely effective over more than a line or two) and that text can be overprinted, but not erased. The LOCATE command is therefore of no use, and steps down the page are usually accomplished by sending a series of carriage returns, by sending special codes that force a printer Ôline feed' (most machines allow you to set the line feed to be of variable height) or that cause the printer to perform a Ôvertical tab' jump. If your printer supports either of the latter two options they will be explained more fully in the accompanying documentation Some printers, especially those that are capable of different print pitches, can also use very many more characters across the page than is possible on the screen. The LPOS keyword returns the value for what the computer feels should be the horizontal position of the print head on the paper, based on how many characters have been sent to the printer since the last carriage return. The situation can actually arise where the computer gets this wrong, e.g. if the printer head was left in an unusual position by a previous program. The keyword is also unable to keep track of what effect, if any, the tab character is producing on the printer (the size of tab jumps can be defined on most machines) but on the whole the information returned should be reliable. LPOS is typically used for testing whether there is enough room on the line remaining to send the next word you wish to print. If there is not, a carriage return/line feed can be sent first ensuring that no words are broken in half. Because of the relative lack of control you have over this feature there is no keyword for returning the vertical position of the print head. As with text printing on screen the WIDTH LPRINT command can be used to set the maximum width of the printed display. After the specified number of characters have been sent a new line is automatically started. Sound HiSOFT BASIC provides many ways of handling sound on your Amiga computer. First of all, here is some (very) simple keywords. We have met BEEP already and there is really nothing more to say about it as it is a very simple command. It is most useful as a means of signalling an error or attracting attention to a message in your program. It is worth noting that a similar effect can be achieved by the command: PRINT CHR$(7) This can be useful when you have written a routine that reads a series of ASCII numbers, say from a file held on disk, and prints them onto the screen. ASCII number 7 can be held within the file to signal the end of a particular message or something like that. Although we've listed BEEP as a sound command on the Amiga as standard it is mute! Never-the-less it is usually thought of as a sound command. The command SOUND is only slightly more sophisticated. It allows the pitch and duration of the note to be varied, and is detailed in the Command Reference chapter. The WAVE command is a useful complement to the keyword SOUND as it allows the type of noise that is to be produced to be controlled by defining the waveform and envelope of the noise. This is again explained in more detail in the Command Reference section. The Timer The timer is not a true physical device on the Amiga computer, but the effect of one is simulated by the operating system as a constantly active background task. As long as the machine is not turned off or reset the current date and time can be read or reset through HiSOFT BASIC. DATE$ and TIME$ are used as functions to return the current value of the Ôclock' settings; the values are returned as specially formatted strings. TIMER is a function that returns the number of seconds since midnight. The returned value is a single-precision numeric value so two calls to the TIMER function can be used to time a given part of the program with much more accuracy than using the TIME$ command (TIME$ returns information as a specially formatted string). TIMER's accuracy is to the 50th of a second, whereas TIME$'s accuracy is 1 second. Try running this program to see the difference between TIMER and TIME$: TIMER and TIME$ If you have a program that wants to do something regularly, may be to update a status display you can use the ON...TIMER statement. Here's a rather silly example: ON TIMER(2) GOTO timer_handler TIMER ON FOR i=1 TO 1000 PRINT i NEXT i STOP timer_handler: PRINT "hello" RETURN Here every 2 seconds (the number in brackets in the ON...TIMER statement, the timer_handler routine will be executed. The TIMER ON statement starts the timer off; if you want to stop a timer for a while (perhaps because you need to do something urgently like download a file) you can do this with the TIMER OFF command; use TIMER ON to switch it back on again. Managing Files HiSOFT BASIC provides a complete suite of commands to allow the programmer to access and control the filing system. All large utility programs you write should employ these commands to allow the user to best organise the layout of files on the disk from within your program, particularly if they may need to create room for data by erasing or copying some of the existing titles. FILES will produce a list of all files present in the current default drive and sub-directory. Try: FILES you should get a list of all files on disk. Now try: FILES "DF0:" you will get a list of those files on the disk in DF0 (assuming there is a disk in there!) KILL filespec will delete the named file from the disk. INPUT "Which file(s) do you want to erase"; A$ KILL A$ It is recommended that you write your routines such that they first request for confirmation before deleting anything in order to check that you will not lose anything vital. NAME filename1 AS filename2 allows a specified file to be renamed. filename1 receives the name that is specified as filename2; note that you can not swap names of files or have two files with the same name. CHDIR changes the default directory. This is helpful when using the FILES and NAME commands, and for using any newly created or modified files (see OPEN, BSAVE etc. later on). Your Amiga manuals will explain how the directory system works, but briefly: each disk drive you have (including a simulated disk drive created in the memory by the ramdisk) will can be referred to by its device name, e.g. DF0:, DF1:, DH0: etc. or by its volume name which is it is given when it is formatted. It is generally best to refer to files on disk by their volume name, that way the system can ask you to put the disk in the drive if it isn't already there and it will prompt you to put your system disk back in when it needs it. On each disk you can store files in one large unstructured group, or choose to organise them into named directories. A directory is a type of filing system folder in which you can store those files that are pertinent to a certain subject. Each directory can have several sub-directories which again can be used to divide the information up into useful groups. Files with identical filenames can be stored in two different directories without causing any problems. For example you can have on a disk called WORK: a directory called ACCOUNTS which can in turn have two sub-directories - OFFICE and HOME. A file called LETTER in the latter sub-directory can be accessed by the pathname: "WORK:ACCOUNTS/HOME/LETTER" The pathname of the file tells the computer the route it will have to take through the various directories to access the data. Using the CHDIR command you can make the sub-directory DF1:ACCOUNTS/HOME the current directory. Files held there can then be accessed by their short name only and commands such as FILES will operate on that specified sub-directory if just a filename is used. MKDIR pathname creates a new sub-directory from within the current default drive and directory or via a specified pathname. RMDIR pathname removes an existing named directory as long as it contains no files at the time. Reading and Writing Disk Files All computers need a system for permanently storing the information that the user has entered, whether this is a program, a list of names and addresses, a screen picture or whatever. Manipulating data files on disk is one of the most important jobs your computer can do. Data storage is increasingly what computers are about, as the development of appropriate hardware such as hard disks drop in price so computer languages are developing to make it easier for the user to access and use data held in a permanent record. There are however different types of disk file that can be used, each appropriate to different circumstances. The simplest type of disk data file you can create is a completely unstructured record of the contents of a specified part of the computer's memory. The most frequently used example of this technique is when a graphical image on the screen, made up from a pattern of dots held in memory, is saved for later recall. The way screen pictures are stored and the implications of this technique are detailed in the section on The Screen. The keywords we use for writing and reading simple memory-image files are BLOAD and BSAVE. To understand how these commands work we must learn something about how the computer's memory is structured. Every item, or byte, of data (which is large enough to store one single letter of a text string for example) is stored at a specified memory address. The amount of memory available varies - on a standard A1200 the number is 2*1024*1024 bytes (a kilobyte is 1024 bytes rather than a thousand, because of the way that computers work internally with binary arithmetic, a megabyte is 1024 kbytes). Not all of memory will be free to use for data - many locations store important working space for the operating system and the programs that are currently loaded. The syntax for BSAVE and BLOAD is: BSAVE "filename", start_address, length BLOAD "filename", start_address The filename can contain a drive identifier and sub-directory path. The restrictions on the use of filenames is rather complex (see your Amiga Manual for an exhaustive list) and it is important to ensure that your program contains a suitable selection of routines to trap nonsense entries by the user. As an alternative to memory-image files it is also possible to define much more highly structured files that hold text and numeric data in special formats such that the computer can at the very least distinguish the length of each entry and its position within the file. The data within such files consists of individual items, normally known as records. It is possible to add or extract specific items of information from these files or to add data items of data to them. There are actually two types of structured file you can use with HiSOFT BASIC. The first is known as a sequential file and it holds data that the computer can only access by stepping through item by item in sequence. For example, you can read the twentieth record in the file only by first reading records 1 to 19. The advantage of such a sequential system is that each individual item of data can be of any length or form that you desire, provided that the end of that particular item is signalled somehow, whether by a comma, by quote marks ("), by carriage returns, or a combination of these. The second type is a random access file. This uses a system whereby each record of data is of a preset and rigid length and form (and we will see later that any data that is not of the right type has to be converted to match) such that the computer can automatically calculate the position of item number 20 and jump straight there - hence the name random access. Because random access files contain items of a preset length, old data can safely be overwritten by new without any danger of obliterating the next entry. Sequential files can only have new data added to the end of existing files. To remove or insert existing items, an old file must be copied item by item into a new file, with the appropriate modifications made during the process. Random access files are therefore often very much faster in practice (although this does depend on the use to which you are putting them). They have the disadvantage that they can be more wasteful of disk space - all records must be allocated the same room regardless of their actual length; this in turn is determined by the longest entry you have to accommodate. The type and structure of a data file is defined when it is created, and this is done by the OPEN command. It is no surprise then that this can be a rather complicated keyword with several parameters: OPEN file_spec [FOR mode] AS [#]channel_num [LEN=record_size] HiSOFT BASIC can have 255 files open at once, so it is fundamental that each of these files is designated by a number as it is opened. Any future disk reading or writing commands can then be told the file they are to work on by referring to that number. Assigning the number is done by using the AS # part of the OPEN command. A simple OPEN "filename" AS #n command will create a new sequential access file, or open an existing one of the given name for reading and modification. However it is possible to define the particular type and nature of the file that has been opened using the following commands: OPEN "name" FOR OUTPUT AS #2 a sequential file for writing. OPEN "name" FOR INPUT AS #9 a sequential file that is to be read. OPEN "name" FOR APPEND AS #1 a sequential file that already exists and is to have data added to the end. OPEN "name" FOR RANDOM AS #5 a random access file The final option is LEN=number. This sets the length of each entry in a random access file. The default size, if the command is omitted, is 128 bytes long. If this command is used for sequential files, it controls the size of the memory buffer that has to be filled before any data actually gets written to disk. Disk operations such as writing data are, in computer terms, relatively slow. The Amiga very sensibly waits until it has a reasonable amount of data to write before it goes to the trouble of moving the disk drive heads around etc. Sequential Files Putting data into a sequential file, and retrieving the data from such a file, in many ways parallels the system used for reading information from the keyboard, and writing to the screen or printer. Indeed so similar are the operations that the keywords used are almost identical. INPUT# and LINE INPUT# allow data to be read from a sequential file. As with input from the keyboard, INPUT # reads data that is to be assigned to a specified list of variables. LINE INPUT# reads an entire line of a sequential file until it reaches a carriage return character, ignoring any commas or other data delimiters and assigning the result to one string. INPUT$(x) can read x bytes of data from either a sequential or a random access file regardless of any data separators or field boundaries it crosses in doing so. As we have seen, putting data into an existing sequential file can only be done by adding it to the end of those records already saved. The keywords to use to do this are WRITE#, PRINT# and PRINT# USING. As with output to the screen, WRITE# automatically separates different data items with commas, and encloses strings within quotes. The full options available with the PRINT# and PRINT# USING commands, and the others, are detailed in the Command Reference chapter. When used to write data to a file they create an exact image of the information that would be seen on screen so certain mistakes are possible if you are not careful. For example: PRINT#1, 1,2,3,4,5 will assume that the commas are to be replaced by spaces as on the screen. If the items were intended to be stored as separate data items the commas have to be PRINTed explicitly as in: PRINT #1, 1;",";2;",";3;",";4;",";5; ÔPRINT# is probably best used for producing formatted lines of text that are to be retrieved by the LINE INPUT# command, or for storing formatted numbers as produced by PRINT# USING. Note the distinction between a PRINTed, and therefore formatted, line of text, as would be produced by a word processor for example, and a structured text file such as may be produced by, say, a database using the WRITE command. In the former case the output will look more meaningful to an observer, but the computer itself will be unable to determine what each individual item of data represents, and where one data entry ends and another begins. In the latter case every item of data is clearly separated from the next by commas and quotes. The WIDTH# keyword can be used to limit the maximum length of any one line of data that is PRINTed to the file. Try the following example: A$ = "John Smith" B$ = "21 New Road" C$ = "London" OPEN "WRITE.DAT" FOR OUTPUT AS 1 WRITE #1, A$, B$, C$ OPEN "PRINT.DAT" FOR OUTPUT AS 2 PRINT #2, A$, B$, C$ CLOSE #1, #2 REM Now read using LINE INPUT OPEN "WRITE.DAT" FOR INPUT AS 1 OPEN "PRINT.DAT" FOR INPUT AS 2 LINE INPUT #1, A$ LINE INPUT #2, B$ PRINT "WRITE data looks like:" PRINT A$ PRINT : PRINT "PRINT data looks like:" PRINT B$ CLOSE The file called WRITE.DAT will have the data stored internally like this: "John Smith","21 New Road","London" The file called PRINT.DAT will have data stored internally like this: John Smith 21 New Road London Any data stored using the PRINT # keyword can be formatted within the file in the same way as it can on the screen: PRINT #2, A$; B$; C$ would produce a file containing John Smith 21 New Road London whereas: PRINT #2,A$ PRINT #2,B$ PRINT #2,C$ will produce a file like this: John Smith 21 New Road London Of course each of these different formats can be accessed in different ways with the INPUT# and LINE INPUT# commands. For example, using the first example file produced with the WRITE# command: OPEN "WRITE.DAT" FOR INPUT AS 1 INPUT #1, A$ PRINT A$ would produce the result: John Smith whereas: OPEN "WRITE.DAT" FOR INPUT AS 1 LINE INPUT #1, A$ PRINT A$ will produce the result: "John Smith","21 New Road","London" The LINE INPUT# command has ignored the data separators and has read a whole line from the file into the variable A$. Conversely in the case of the first PRINT example: OPEN "PRINT.DAT" FOR INPUT AS 1 INPUT #1, A$ PRINT A$ would produce the result John Smith 21 New Road London The INPUT # keyword has found no recognisable data separator to denote where one entry finishes and the next begins and: OPEN "PRINT.DAT" FOR INPUT AS 1 LINE INPUT #1, A$ PRINT A$ would produce the same result. Obviously, different techniques are required depending on how you choose to store data from within your programs. Random Access Files When using random access files things are rather different. The rather inflexible format with which these files are stored requires a much more complex system of data manipulation before the information can be stored on, or retrieved from, disk. To recap, random access files are divided into records, but these are of a fixed length, defined at the time the file is opened, and can only contain data in a fixed format (actually stored as binary data) whether it started off as text or numbers of any precision. Many different keywords are therefore required to convert the data into the specified length and the required form. Random access files have one or two other important traits. Firstly the data held within a given record can in, turn, be subdivided into a series of fields each of which hold a subset of information that is pertinent to the whole record. For example, in a club mailing list file, each member would merit a record of their own, but within that record there will probably be a field for NAME, one for ADDRESS, one for TELEPHONE NUMBER etc. The keyword FIELD is used to allocate space within the total record to each sub-division: FIELD 1, 20 AS name$, 50 AS address$, 12 AS tele$ Each string variable that is defined in the FIELD statement is called, of all things, a fielded variable. Fielded variables are rather special and should only really be used in conjunction with the RSET and LSET commands that we have already met, in order to produce a precisely-formatted string. Data can not be allocated to these variables using other commands such as INPUT or they will lose their Ôfielded' status. A FIELD statement must be issued before reading or writing a random access file. Even if the records are not subdivided, at least one field string must still be defined to hold the data that is to be written to the file. Because all data held within a random access record has to be of the defined length then if it is too long it will be truncated and if it is too short it has to be padded out to fit. To do this all data is converted to a string form of the required length, before being saved. To recover it again it has to be converted back from a string to the data form it originally started with. The process of writing data to a random access file is therefore as follows. Firstly all numeric data has to be converted to a string. The following keywords do this job: MKI$(x) converts an integer x to a string. MKL$(x) converts a long integer x to a string. MKS$(x) converts a single precision number x to a string. MKD$(x) converts a double precision number x to a string. The next step is that the resulting string has to be placed in the random access buffer, an area of memory automatically reserved as workspace when the file is opened, ready for writing to the disk. As this is done the data is padded to the length required to fit either the whole record or a field to which it is to be assigned. This is done through the use of the keywords LSET or RSET. LSET fits the given string to the left hand end of a padded string of the required length. RSET fits it to the right hand end of a padded string of the required length. For example the command: RSET A$=MKL$(x) will fit the string equivalent of the long integer variable x, into the right hand edge of the fielded variable (A$) ready for storage. If the name of the variable, A$, has been designated as a field occupying a subsection of the total record length (see above) the computer will automatically store the data in the correct place within the total buffer. In this case RSET or LSET will place the data into the right or left hand end of the room allocated to that variable. Remember that MKtype$ is only necessary when storing numeric data. Commands such as: RSET A$=B$ or: RSET A$="Fred Smith" can be used to place text data directly into a fielded variable. When all of the fielded variables have been assigned the appropriate data, or left blank if required, the buffer memory can be copied onto the actual disk. The command required is ÔPUT#n, x, which writes the current value of the buffer data to the correct place within file number n, as record number x. If x is omitted the next record in the file is written. The buffer is Ôemptied' as a result of this command so that the next record to be written will be starting with a clean sheet, as it were. That was many new concepts to take in, but the process is really very simple if you take it step by step. Here is a simple example: A$="John Smith" B$="21 New Road London" X%=21 OPEN "PERSONAL.DAT" FOR RANDOM AS #1 LEN=65 FIELD 1, 20 AS name$, 40 AS address$, 5 AS age$ LSET name$=A$ LSET address$=B$ LSET age$=MKI$(X%) PUT 1,1 Retrieving data from a random access file is a slightly simpler process. The opposite of the PUT command is GET. Before issuing this command, the field variables should again have been declared in the program. Once one record has been read these fielded variables can then be used immediately, for example to print individual sections of the data, or the retrieved data can be assigned to other variables for manipulation freeing the fielded variables for reading another record. If the data that has been retrieved was originally assigned to a numeric variable it can be converted back to its original form using one of the following commands. CVI(string$) converts the data back to an integer. CVL(string$) converts data back to a long integer. CVS(string$) converts data back to a single precision number. CVD(string$) converts data back to a double precision number. Note that these commands work in a very different way to the VAL keyword, and they cannot be used in its place. Note also that random access files require that you can anticipate the likely precision of numbers before they are converted. Sequential files are more tolerant in this respect - a numeric variable of an unspecified type can be used to hold a given value and that variable will to an extent adjust its internal format to match the data it is assigned. OPEN "PERSONAL.DAT" FOR RANDOM AS #1 LEN=65 FIELD 1, 20 AS name$, 40 AS address$, 5 AS age$ GET 1,1 PRINT name$; CVI(age$) will produce the output John Smith 21 Note that the variable name$ is padded with spaces to the length defined in the field statement; you can use RTRIM$ to cut this data down to size. Finally we have the keywords LOF, LOC and EOF which are three functions that allow the program to keep track of the size of a file and the current position of the pointer within it. LOF stands for Length Of File (in bytes). LOC stands for the LOCation of the data pointer. When using random access files it reports which number entry was read by the last GET command or was last written to by PUT. For sequential files LOC is often less useful. It returns the current byte position of the file pointer divided by 128. If your records are of a set length the actual record pointed to can be calculated from this value even if your data entries are longer or shorter than 128 bytes. EOF, standing for End Of File, is an invaluable logical function that reports TRUE (-1) when the data pointer has reached the last data entry. It is usually used in logical loops to trigger the end of a certain operation as in this example which reads an ASCII file and copies it to the printer: SUB Print_File FILES INPUT "Which file do you want to print?"; A$ OPEN A$ FOR INPUT AS #1 WHILE NOT EOF(1) LINE INPUT #1,aline$ LPRINT aline$ WEND CLOSE #1 END SUB Shutting down a file is a very simple affair - just use the command CLOSE for either sequential or random access data files. A list of numbers can be given to close several files at once, and the keyword CLOSE without any specified numbers shuts down all files. The keyword RESET has the same effect. All FIELD statements are forgotten at the same time. The various program termination commands STOP, END and SYSTEM also automatically shut down all files. All OPENed files must be closed if the disk is to be removed while the program is running or data may be ruined. At the very least any data still held in memory buffers will be lost. Because of the threat of power cuts you can consider including a routine in all your programs that, perhaps at a timed interval, makes a backup copy of the current files. The final command that may be of value is the keyword VARPTR#. This will return the address of the buffer used to store information that has just been read from, or just prepared for sending to, the file number. This keyword is principally designed to pass information to routines written in other languages such as assembler to allow them to access the data held by your HiSOFT BASIC program. Error Handling Error Handling is a means whereby the programmer can anticipate, and make allowance for, undesirable circumstances whilst the program is running. These Ôerrors' are of three main types. They may result from a problem in the hardware such as no paper in the printer, no room left on the disk or no disk in the disk drive. They may follow an incorrect data entry or response by the user. Finally they may be triggered by some error in the programming that did not become apparent under test. HiSOFT BASIC, in conjunction with the Amiga operating system can pick up, and determine the nature of, a wide selection of errors and it is good practice that your program should try to prepare for as many of these as possible. Error trapping routines can be tedious to plan and write, and can seem to take a disproportionate amount of time compared to the real nuts and bolts of your program, but if done well they are the hallmark of a professional quality program. We have already seen how it is possible to enter simple error traps by specifying certain logical conditions that accept or reject data entered by the user. For example: DO LOCATE 1,2 INPUT "Size of curve (1-4) ",s% y%=length/s% LOOP WHILE s%>=1 AND s%<=4 This continues to ask the question until the user puts on his spectacles and provides an acceptable answer. Note also that the variable s% is an integer type so that an entry such as 1.4 would be converted to a usable number. However, it is part of the nature of error catching routines that there are always more possible ways that the user can catch you out than you will ever anticipate in the first attempt; let's say he enters the value 0 - you've forgotten to trap this and you will get a runtime error on the fourth line because of division by zero. It is also inevitable that Ôerrors' can occur that you will be unable to prevent happening, such as an unformatted disc being placed in the disc drive and so on. We therefore need a system of dealing with such errors no matter when and how they occur. Errors that are produced through a problem in the hardware or through a mistake in the program, such as trying to divide an expression by a variable which has somehow been assigned a value of zero, will generate an error number specific to that particular situation. To allow the maximum use of this facility there are a range of functions that will report the nature of the error, together with the line number which was being executed when the problem occurred. ERR is the function that returns the error code and ERL returns the line number where it happened. From these your routines can deduce what the problem was and decide how to fix it (e.g. print a message such as There is no disk in the drive and then restart the data input routine). The nature of errors means that they can only be anticipated to occur at un-anticipated times. Similarly the response to the error has to be ready to come into operation at any time. Hence error handling really has to be organised as a background event trap. The command sequence we use is therefore is ON ERROR GOTO which causes a jump to a specific line number or line label as soon as any error occurs. The line that is jumped to will usually be the beginning of a routine that decides exactly which error has occurred, deals with it in some way, and decides where (if at all) that the program is to resume execution. The keyword RESUME specifies which line number or line label the program is to return to following the specific error correction routine has been completed. Here is an example: a$="TEST.DAT" Kill_File: ON ERROR GOTO Kill_Problem KILL a$ ON ERROR GOTO 0 Ô Normal error reports STOP Kill_Problem: ErrNum=ERR REM Can't use ERR directly in CASE SELECT CASE ErrNum 'Error 53 is File not Found CASE 53 FILES PRINT a$;" not found" INPUT "Please give a valid file";a$ CASE ELSE PRINT "Fatal error ";ERR : STOP END SELECT RESUME Kill_File As an extension of the error facility the programmer can also add to the list of errors that will be reported by specifying new conditions that are to be regarded as problems of some kind. The keyword ERROR can be used in this context to define the error that is required. It can be used to extend the range of conditions under which an existing error number is triggered: For example: runtime error 5 is Illegal Function Call - you might want to use that in one of your own user-defined functions as shown on the next page: REM Factorials by iteration FUNCTION Factorial#(x) STATIC Temp#,i REM Can't do 0! or 1! IF x<2 THEN ERROR 5 Temp#=1 FOR i=2 to x Temp#=Temp#*i NEXT i Factorial#=Temp# END FUNCTION The ERROR keyword can also be used to define the conditions that would trigger an error number that has not previously been used. This is invaluable when using unusual peripherals for which there are no pre-defined errors, and can even be used to catch situations such as when a data input has exceeded a permissible maximum, or is not of the appropriate format etc. Get_Input: ON ERROR GOTO Validate INPUT "Choice (1-4)";Choice IF Choice<1 OR Choice >4 THEN ERROR 200 INPUT "Name, please"; Name$ IF LEN(Name$) > 19 THEN ERROR 201 REM Normal error reporting ON ERROR GOTO 0 Validate: ErrNum=ERR SELECT CASE ErrNum CASE 200 PRINT"Please select a number between 1 and 4" CASE 201 PRINT "Names must be less than 20 characters" CASE ELSE PRINT "Unknown validation error" : STOP END SELECT RESUME Get_Input This routine most usefully works in conjunction with an ON ERROR statement as the newly defined number can then, if triggered, cause a jump to a sub-program which will print an explanatory comment. If error handling is not enabled the effect of the newly defined error, when triggered, is to print the message Runtime error nn where nn is the error number. End Sub That was a tour of the concepts that underlie HiSOFT BASIC. Hopefully it has provided you with a good enough grounding to begin to experiment with the various commands in detail. There are several ways to expand your BASIC programming skills. Firstly practice. You will get a feel for how the different commands work by trying them out in routines of your own devising. Secondly type in routines from magazines and books (you will be pleasantly surprised by how many listings work in HiSOFT BASIC with only the minimum of modification so you do not even have to restrict yourself to Amiga programs). There is never one correct way of programming a given task, any dozen people would probably come up with a dozen different solutions to a given problem. Entering listings is therefore an invaluable way of learning new ways of looking at things. Remember, you do not have to re-invent the wheel every time you sit down at the keyboard. Many routines have already been created that can be used within your own programs, probably with greater speed or using less memory space than you would have been able to come up with on your own. The bibliography lists a selection of books that may be useful in teaching programming techniques.