Previous chapter Index Next chapter

Chapter 3

Real programming

3.1 Designing programs

As described in the previous chapter, a computer program is simply a collection of numbered instructions you give to the computer, to tell it how to perform a task. Programming is the collection of activities needed to produce a computer program.

Developing even the simplest program will involve you in a number of different tasks. A typical breakdown of activities in the programming of a fairly serious application might be as follows:

These tasks will be re-examined in great detail in later chapters. For now, the most important thing to realise is that you should design a program before beginning to write it.

To design a program, you must first analyse the task you want the computer to perform into its constituent sub-tasks. When you understand what each sub-task must do, you translate each into a specification of the steps that the computer must take to perform the whole task, expressed using the words and concepts that the programming language provides.

All programs take information, manipulate it in some way, then use the manipulated information to produce results. One of the simplest ways of breaking down a task, then, is to consider it as these three stages:

Typically, information is typed by the user or read from the program or a disc; is manipulated to produce totals, averages, sorted lists, etc; and is then printed on the screen or on paper as numbers, text, bar charts etc. or stored on a disc for later use by this or other programs.

Getting information

When a program is run, the information that it uses can come from three main sources: from within the program, from the user typing it, or from files stored on discs. Each of these will now be described in turn.

3.2.1 Information storage in the program

If the information that a program needs is fixed, the simplest way to provide it is within the program itself. The simplest way is by using constants, either in instructions:

330 PRINT "***SUB-TOTALS***"

or by assigning the constant to a variable and using that instead:
330 title$ = "***SUB-TOTALS***"

340 PRINT title$

You have already used this method in the example programs and will rarely write a program that does not use it to some extent. Using a variable to hold a constant value in this way has several advantages over using the constant itself:

If you use lists of constants in a program, instead of assigning each in turn, you can use a pair of keywords called DATA and READ. DATA is followed by a list of string or numeric values, separated by commas. READ is followed by a variable (or list of variables separated by commas) to which it assigns constants in turn from the DATA list.

For example, if you wanted to store the number of days in each calendar month, you could use the following DATA statement:

100 DATA 31,28,31,30,31,30,31,31,30,31,30,31

Then use the following READ instructions to assign the information:

110 READ jan.days

120 READ feb.days

130 READ mar.days

etc.

To save typing, you could include several variables in the READ instruction:

110 READ jan.days,feb.days,mar.days

READ and DATA are particularly useful for assigning constants to a special type of variable called arrays; see Section 3.3.7, below.

You may have any number of DATA statements in a program. BASIC treats them as if they were a single list of constants, starting with the DATA statement with the lowest line number. Each time READ is used to read a value, the next one is read from the list. If you try to READ more values than are in the list, and error is generated.

A further command, RESTORE, can be used to alter the order in which values are read - but this command is outside the scope of this introduction to BASIC. Details are given in 'Mallard BASIC: Introduction and Reference'.

3.2.2 Getting information from the user

The programs that you have so far learned to write have had a major limitation: they each have a single, fixed purpose. If you want one of these programs to do something even very slightly different, you have to change the program!

For example, say you run a (very!) simple retail operation, selling a single product and want a program to tell you how much to change for it to make a 20% profit, the following program would do:

10 cost=10

20 profit=20/100

30 PRINT "The price to charge is ";cost+cost*profit

Type this program in and run it. (Don't forget to type NEW [Enter] first. Use AUTO if you want.)

The program works well but is rather limited: if the item price changes, you must retype line number 10; if the required profit changes, you must retype line number 20. This is rather tedious and somewhat error-prone. (Try it and see.) It also means that only someone who knows how to modify the program can use it to calculate the price when the cost or profit margin changes.

The real problem is with lines 10 and 20, because they contain information that is constant, while you want the program to cater for information that varies. BASIC provides a solution, in the form of a command called INPUT, which asks for information when the program is run then assigns it to specific variables.

To see it in action, change line 10 to the following:

10 INPUT cost

When you run the program now, it displays a '?' and waits. The program is waiting for you to type a number, followed by [Enter]. It will then assign the number to the variable cost. Type a number and press [Enter]. Run the program again, typing a different number.

The changed program is now:
10 
INPUT cost
\----v---/
changed

20 profit=20/100

30 PRINT "The price to charge is ";cost+cost*profit

INPUT can also be used to assign text to a string variable, by simply using a string variable in the instruction (such as INPUT name$). There are certain limitations on the text that this can handle (it does not, for example, cope with commas as you would expect), but these can be overcome by using a LINE INPUT command. (For details, refer to 'Mallard BASIC: Introduction and Reference').

INPUT has successfully made your program a bit more versatile, but in its new form, the program is still a little daunting to use. When you run it, it is not obvious just what you should do when the '?' prompt is displayed; the program should give a little help. You have already met a command that can be used here: PRINT with a text string.

Modify the program to display a message (prompt) telling the user what to do when they see the '?'. Try the modified program and see if this helps.

Here is one solution:
5  PRINT 
"Type the item cost price, then press RETURN "
\----------------------v--------------------/
prompt (text string)

10 INPUT cost

20 profit=20/100

30 PRINT "The price to charge is ";cost+cost*profit

Did you remember to put the prompt before line 10? The particular text chosen for a prompt is totally irrelevant to how the program works, but is VERY important to the people who use the program. Carefully worded prompts make a program much easier to use.

Because INPUT often needs a prompt to explain to users what they should type, BASIC allows you to include a prompt in the INPUT instruction. Using this, lines 5 and 10 can be combined, as follows:
10 INPUT 
"Type the item cost price, then press RETURN ",
cost
\----------------------v--------------------/ \-v-/
prompt variable

20 profit=20/100

30 PRINT "The price to charge is ";cost+cost*profit

Note that a prompt used in an INPUT instruction must be a constant text string, not a string variable. (Can you think why? If it were a variable, INPUT would try to assign a value to it, not display it as a prompt!) Also the prompt must come before the variable, not after it.

Note the comma after the text string. This suppresses the '?' prompt that BASIC would normally display after the text string. If you want the '?' displayed, use a semicolon instead of a comma.

Save the program on your disc, with the name "MARKUP". We will return to it later on. Then modify the program to prompt for a profit value too, run it and check that it works correctly.

BASIC provides another method of getting information typed by the user, in the form of the INKEY$ function. INKEY$ quickly examings the keyboard to see if a key has been pressed and either returns it as a single character string, or returns a null string if no key is pressed. An instruction using INKEY$ takes the form:

   variable$ = INKEY$

Since INKEY$ does not wait for the user to press [Enter] (or any other key), it is used quite differently to INPUT. It has two main uses: to check on what the user has typed while carrying on with some other task, and to provide a quick and easy single-character input.

Unfortunately, both uses require BASIC commands that have not been introduced yet. To see INKEY$ in action, you will have to wait for the next chapter!

3.2.3 Getting information from disc

The third main source of information is from data files stored on discs. Data files are created using BASIC, to hold numeric and string information, rather like variables. However, unlike variables, the information in files is preserved until you deliberately change it: it is not lost when you do things like change programs or switch the computer off.

Using data files is described in detail in Chapters 6 and 7.

3.3 How information is stored

This section describes the types of variables and information that BASIC can use.

3.3.1 Choosing variable names

BASIC leaves the choice of variable name largely up to the programmer. The main options (and restrictions) are as follows:

Finally, remember that the case (capitals/small letters) of variables is ignored. (The variable name$ is the same as Name$, NAMe$, etc. etc.)

Within these limitations, you should choose variable names so that they indicate what the variable is being used for. For instance, if a variable is used to maintain a trial balance, use the variable name balance or even trial.balance, not something completely obscure like zz. This will make it far easier for you to understand your programs when you come back to correct or improve them later on.

You should however keep variable names reasonably short, to keep your programs compact and reduce the number of typing errors you will make. Try typing

trial.balance.carried.forward = trial.balance.carried.forward + 1

a few times and I think you'll agree!

3.3.2 The short life of a variable

Variables are by their nature rather flighty, transient things. The current value of a variable is lost whenever you:

If you try to use a variable for which the value has been lost, it will give 0 (for a numeric variable) or the null string "" (for a string variable).

There are several ways to preserve the information contained in variables so that you can use them later, but for now just remember when writing a program to assign the required values to variables before you use them; don't assume they will be preserved from the last time you used a program: they won't.

3.3.3 Numbers

When typing numbers for BASIC to use, you can use a variety of forms, as follows:

The most familiar numbers are what are known as 'unscaled numbers': integers and decimal numbers, such as -44444, 0, 3.111, 3333333333.77702. This is the form of numbers we see and use every day and will probably be mose useful for your programming. Note however that you must not include commas or spaces in these numbers.

Probably less familiar (unless you have a scientific background) are 'scaled numbers' (otherwise known as scientific format numbers), in which the number is expressed as a decimal number multiplied by the number 10 raised to a positive or negative number. They take the form n.nnnE[sign]nn, or n.nnnD[sign]nn. Scaled numbers are mainly useful for expressing very large and very small numbers. (The E and D portions of the number dictate the accuracy with which BASIC stores the number.)

Finally, BASIC provides 'based numbers' to let you express numbers in octal (to base 8) or hexadecimal (to base 16). Octal numbers are preceded by &O; hexadecimal numbers by &H. Based numbers will mainly be of interest if you are used to low level programming with minicomputers or microcomputers.

Typical numbers in each of these representations are as follows:
UnscaledScaledOctalHexadecimal
0 0E0 &O0&H0
100 1E2 &O144&H64
-100 -1E2 &O177634&HFF9C
100.11.001E2 - -
0.0232.3E-2 - -
The allowed range of each format is as follows:
-1.7E38 -1.7E38 &O0 &H0
to +1.7E38to +1.7E38to &O177777to &HFFFF

3.3.4 Numeric variables

Regardless of how the number was represented, BASIC only has three types of numeric variable to store it in: integer, single precision and double precision. These differ in the amount of memory storage required, how fast BASIC can manipulate them and the accuracy with which they can store different types of number. Once a number has been stored in one of these variables, the original representation is forgotten.

Integer variables can only hold whole numbers (ie. without fractional or decimal parts), in the range -32768 to 32767. Any decimal portion is rounded when the number is assigned to an integer variable, while numbers outside the specified range cause an error.

Single precision and double precision variables can store numbers including a decimal part in the approximate range

-100000000000000000000000000000000000000 to 100000000000000000000000000000000000000
(-1E38 to +1E38 in scientific notation). Assigning numbers outside this range will cause an error. Single precision variables are accurate to approximately seven significant digits; double precision to approximately sixteen digits. The smallest number that a double precision variable can store that is distinct from 0 is approximately 3E-39.

The type of a numeric variable is indicated by the last character of the variable name, as follows:

%
means integer
!
means single precision
#
means double precision

Anything else is taken as single precision.

3.3.5 Text strings

Text strings are sequences of zero or more characters, surrounded by double quotes ("). Typical text strings are:

  ""

  "and did those feet"

  "Phillip.P.Jones"

  "23"

Note that the last is only a string because it is enclosed in quotes; without the quotes it would of course be a number.

What is actually held in the string is not the characters themselves but the 8-bit codes used to represent these characters. These codes are combinations of 0s and 1s in the range 00000000 to 11111111, but are most easily thought of either as hexadecimal numbers in the range &H00...&HFF or as decimal numbers in the range 0...255. The number corresponding to a character is known as the character's internal value.

A consequence of this is that when you print a text string either on the screen or on a printer, BASIC sends the codes for the characters to the output device and it is up to the software controlling the device to translate these codes back into characters. However, the code used for each character depends on the software you are using and your printer, in particular, may not use the same set of codes as your computer - especially as ASCII, the acknowledged standard set of codes, only defines the characters associated with codes in the range &H00...&H7F. So when you go outside the range of characters in the ASCII set (A...Z, a...z, 0...9 and the standard punctuation marks) you can find yourself entering one character and printing another!

Strings can contain any 'character' code between 0 and 255 but not all codes can be entered by typing "character": in particular you cannot use this method to enter some of the codes because these are used to represent actions, such as starting a new line or a new page, rather than printable characters. However, there is a BASIC function, CHR$, which can be used to enter any code. You just need to know the internal value of the code. We will see how CHR$ is used later in this chapter when we use these 'Control' codes to control the screen and the printer.

3.3.6 String variables

String variables are distinguished from other types by a suffix of $. The maximum number of characters that can be stored in a string variable is 255; the minimum is zero (the null string, ""). Characters with internal values anywhere between 0 and 255 can be stored in string variables.

3.3.7 Organising variables (arrays)

The variables described up until now have all have quite independent, separate, existences. While this is entirely satisfactory for programs that simply take information, manipulate it and print the results, there are times when you will want to hold lists or tables of information in variables, preserving the relationship between them.

One way of doing this is to use related variable names. For example, to store the names of record albums, you could use the string variables album1$, album2$, album3$ etc.

While this method of naming makes the relationship obvious to you, BASIC entirely misses the point - it sees no ordering or grouping in these names. For instance, to print the name of album 2, you need the instruction:

   PRINT album2$

To print the name of album 3, you need a different instruction:
   PRINT album3$

And so on, for each name. Even more seriously, there is no way (with the commands described so far) to construct a program to perform simple, useful tasks like printing the album name from its number.

Luckily, BASIC provides a way to solve these problems by organising variables into an arrangement called an 'array'. A variable in an array is called (naturally enough) an 'array variable' or 'array element'.

An array consists of a group of variables with the same name and type. In the simplest form of array, the variables are organised into a list, being distinguished by a numeric value in brackets added to the end of the variable name. This value is know as the 'array index' and is 0 for the first element, 1 for the next one and so on. Note: This is not the same as Spectrum BASIC where the first element has the index 1.

Arrays can be made using any of the types of variables described above. For example, while album$ is a string variable, album$(6) is a (string) array variable and album(3) is a (numeric) array variable. Note that all these variables are distinct and that, unlike Spectrum BASIC, you don't have to define the length of the elements in a string array and you aren't limited to single letter names for your arrays.

Array variables can be used exactly as the equivalent (non-array) variable type, but their main use is indicated above: in processing lists or tables of information. For example, to solve the problem posed above, the following program will suffice:

10 album$(0)="Dark side of the moon"

20 album$(1)="Vivaldi's four seasons"

30 album$(2)="A bridge over troubled water"

40 INPUT "What number album do you want a title for";number

50 PRINT album$(number)

Lines 10 to 30 give values to the first three elements in the array album$(). Line 40 gets the album number wanted by the user. Line 50 prints the string stored in the corresponding array variable.

If you are going to use an array with not more than ten elements, or want to use fewer elements and not waste memory, you should 'dimension' the array before using it, with the command DIM.

DIM is followed by the array name and the maximum subscript to be used in brackets, for example:

  10 DIM album$(3)

Arrays can have many dimensions, not just the single one of album$() above. For instance, if you wanted to record the temperature at a number of points within a room, you might use temp(x,y,z) where x, y and z are the co-ordinates of the point within the room, chosen so that no two points will have the same x, y and z co-ordinates. If you wanted to record how this information changed with time, you could use temp(x,y,z,t) where t is the time scale, and so on. Before you can use an array with more than one dimension, you must dimension the array using DIM (for example, DIM temp(10,10,15)).

Note that an array cannot be used as a variable, only the elements in it. An array is just a way of organising variables for your convenience.

The quickest and easiest way of setting up an array from constants is to use the keywords DATA and READ, introduced above. Contrast the following program fragments, both of which store the days in the month in an array called 'days'. The first uses DATA, READ and a FOR...NEXT loop to produce code which is compact and easy to understand; the second does not. The FOR...NEXT loop will be explained in full in Section 4.2.

10  DIM days(12)

20  DATA 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31

30  FOR month = 1 TO 12

40    READ days(month)

50  NEXT





10  DIM days(12)

20  days(1)  = 31

30  days(2)  = 28

40  days(3)  = 31

50  days(4)  = 30

60  days(5)  = 31

70  days(6)  = 30

80  days(7)  = 31

90  days(8)  = 31

100 days(9)  = 30

110 days(10) = 31

120 days(11) = 30

130 days(12) = 31

3.3.8 Choosing variable types

The choice of variable type can be broken into two levels: numeric or string, and integer or single or double precision.

String variables must be used if you want to store text (numeric variables cannot be used for this), but can also be used to store numbers if they are not going to be manipulated as numbers.

Integer variables should be used whenever you don't need to store fractional information and the number is in the permitted range (-32768 to +32767) because integer variables produce smaller, faster programs.

Single precision variables will probably be sufficient for most other uses. As a rough guide, you should only need to use double precision variables if you want to preserve more than six significant digits in your calculations. Double precision variables take up more memory and considerably increase the time taken to perform calculations.

An awkaward point to bear in mind is that while we think and calculate in decimal, most computers do so in binary, converting decimal numbers to binary for storage and calculation, converting the binary back to decimal for display. Fractional numbers which can be expressed exactly in decimal, can't necessarily be expressed exactly in binary and as a result the answers to calculations may be less accurate than you expect. For example, the result of a simple calculation might be 0.99999998 rather than 1.

This 'inaccuracy' is a direct result of expressing floating point numbers in binary and it affects all programs that carry out floating point arithmetic. Another effect is that numbers that appear the same when you print them aren't necessarily identical, because the displayed numbers will probably have been 'rounded' to some number of decimal places. In practice, you should always round any calculated result before using it, with the BASIC function ROUND (see Section 5.3.2).

Array variables are generally useful when the information being stored is obviously ordered, into lists or tables, and you want to preserve that ordering. A typical example of a table of data is an address list, which could be stored using separate arrays for the name, address and telephone number, linking them for each individual by a shared index number (eg. name$(22) plus address$(22) plus phone%(22) as the entry for one individual).

3.4 Outputting the results

The purpose of any program is to produce tangible, usable results. This is described as the program's Output.

There are two main types of output: visible (human-readable) output on the screen or on a printer and 'invisible' (machine-readable) output which is usually stored on disc prior to being processed by another program. What we shall look at here is visible output. Output to disc is described in Chapter 6.

3.4.1 Output to the screen

The command used to send output to the screen is PRINT. This command has already been used extensively in previous examples but in a rather limited form. All we have done is print single items, either on separate lines or one after the other on the same line.

The display these simple commands produced has often been rather untidy, with information split between lines and with similar information on different lines not lining up vertically. In fact, PRINT instructions can give you much greater control over where each piece of information is displayed.

The simplest PRINT instruction consists of just the keyword PRINT. This moves the cursor to the start of the next line and is most frequently used to produce a blank line. More complex PRINT instructions follow the keyword PRINT by combinations of items to print and instructions that control their printing.

The items to print must be separated by columns or semicolons. If the two items are separated by a semicolon, the second item is printed immediately after the first, without leaving a space. However, if the items are separated by a comma, the second item is positioned at the start of the next print zone.

Print zones have an effect rather like tab stops on a typewriter: they enable you to put your output into columns. To see the effect of the print zones, type and run the following program:

10 PRINT "Print zone number"

20 PRINT "1","2","3","4","5","6"

Initially the zones are 15 characters wide (effectively, giving you a tab stop every 15 characters across the screen), but you can change the width of the zones to n characters by using a ZONE n command. For example, you could make each column 10 characters wide by having a program line containing the instruction ZONE 10.

The items to be printed may be numeric or string constants, variables or expressions, or the 'print functions' SPC(n) and TAB(n). Printing SPC(n) prints n spaces; printing TAB(n) moves the cursor to column n on the current line, or to column n on the next line if the cursor is to the right of column n. So, for example, you could print three numbers separated by six spaces with an instruction like:

   PRINT number.1;SPC(6);number.2;SPC(6);number.3

After all the items in the print instruction have been printed, the cursor normally moves to the start of the next line. However, if the instruction ends in a comma, semicolon, SPC(n) or TAB(n), the cursor will stay where it is. To see this effect, run the following program and note how the semicolon 'joins up' the output from lines 30 and 40.

10 PRINT "Different"

20 PRINT "lines"

30 PRINT "The same ";

40 PRINT "line"

Controlling the format of the individual items

The PRINT commands we have used so far have displayed your list items 'free format' - that is, according to a simple set of standard rules.

If you are not happy with the way PRINT displays print items, you can give your own specification of how they should be shown. To do this, you include the keyword USING in your PRINT statement, followed by a 'format template'. Format templates give you a wide range of options in presenting text and particularly numbers. Full details of format are outside the scope of this introduction, but the following example should give you an idea of the flexibility provided.

Suppose you are handling amounts of money in a program. Naturally, you will want to tabulate these neatly. At first glance, you might think that all you need to do to produce a neat table is to use a TAB function, as in line 50 below:

10 sum(1)=1.24

20 sum(2)=0.333

30 sum(3)=1444

40 FOR count = 1 TO 3

50 PRINT TAB(20); sum(count)

60 NEXT

Type this program and then run it. As you will see, the figures line up but by their first character - not by the decimal point as you would like.

You can make the decimal points line up if you change line 50 to:

50 PRINT TAB(20); USING "###,###.###"; sum(count)

The "###,###.##" is the format template that is to be used for sum(count). The pattern of #s specifies that each number should be displayed as six figures to the left of the decimal point (if necessary, padded out with spaces) followed by two to the right of the decimal place (with the second decimal place rounded). The comma specifies that the figures to the left of the decimal should be grouped in threes as shown in the template, with the groups separated by commas.

Run the revised program to see its effect.

3.4.2 Output to the printer

Outputting information to the printer uses the LPRINT command, rather than the PRINT command, but is otherwise just like outputting information to the screen. The instructions used are very similar and all the print functions described above (TAB, SPC) and the format templates have the same effect.

3.5 Controlling the output device

Print functions and format templates don't represent the only control you have over how your output is presented. You can also control the device itself. In the case of screen output, this means you can choose whereabouts on the screen each print item is placed - and you can clear away information you no longer require. On a printer, as well as moving the print position, you can select different print modes and control such things as the number of characters per inch across the page (the Character pitch) and the distance between one line and the next (the Line Pitch).

The way you control these devices is by sending some special sequences of 8-bit codes to the device. These control sequences are either single characters with internal values in the range 0...31 (Control codes), or short groups of two or three characters, the first of which is the 'Escape' character (internal value 27; also known as ESC). The groups of characters are recognised by the device as control sequences and are acted upon accordingly.

A number of programs contain the facilities to send control sequences to the screen and the printer. What we describe here is how you send these control sequences from BASIC.

3.5.1 Controlling the screen

The actual facilities and the codes required depend on the computer you are using and the operating system you are running. However, most systems are designed to provide the same facilities and use the same control sequences as one of the standard computer terminals - in particular, the VT52 and ADM3A terminals.

CP/M Plus, on the Spectrum +3, provides broadly the same facilities as and uses the same control sequences as a VT52 terminal. The codes are listed in Appendix III 'Terminal characteristics'.

The best way of showing how to make use of these codes is through an example. Suppose, for instance, that you wanted to:

Looking at the appendix on 'Terminal Characteristics', you will see that you will need:

(The rows referred to here are the lines of the screen and the columns are the character positions of the screen, all characters on the screen being displayed on a rectangular grid, so many lines deep and so many lines wide).

To get the result you require, you should first send the escape sequence ESC E to clear the screen; follow this with the escape sequence ESC H to position the cursor at the the top of the screen; then print the title 'Screen control'; and finally send the escape sequence ESC Y 47 54 to position the cursor at line 15, column 22 (47 is 15+32; 54 is 22+32).

The technique BASIC uses to send control sequences to the screen takes advantage of the fact that both control sequences and text strings are made up of 8-bit codes. So just as you use PRINT statements to send text strings to the screen, so you use PRINT statements to send the control sequences to the screen. The only complication is how you enter a control sequence that is given here as ESC Y 47 54, for example, in your PRINT statement.

The first stage is to understand what ESC Y 47 54 represents. Each part of this control sequence is a separate code. These codes can be written in one of three ways - as the code's 'name', as the character associated with the code (where this is a printable character, like Y), or as the corresponding internal value. In the case of ESC Y 47 54, ESC is the code's name, Y is the character associated with the code and 47 and 54 are internal values. (You can always assume that straightforward numbers are internal values; if the code corresponding to one of the characters 0...9 is required, then it will usually be written as "0"..."9" to make this clear.)

The techniques used to express these codes are simply those used to enter characters into a text string.

Codes with special names don't represent printable characters and you express them using CHR$(value) where value is the code's internal value. For example, ESC has the internal value 27 and so to specify ESC, you put CHR$(27).

You can put the codes of the escape sequence into the PRINT statement as individual print items, separated by semicolons. (Alternatively, you can combine them into a string of characters by using the + operator, described in Section 5.2.2 'Joining strings'.) So the complete sequence ESC Y 47 54 could be sent as:

   PRINT CHR$(27);"Y";CHR$(47);CHR$(54);

(The final semicolon is included to prevent the cursor from moving to the start of the next line on the screen after completing this instruction - as described above.)

As we emphasised earlier, using variable names rather than straightforward values makes a program easier to understand and so we would recommend defining any escape sequences and control codes you plan to use at the beginning of your program. For the series of actions we suggested above, you might start your program with:

10  escape$=CHR$(27)

20  clear$=escape$+"E"

30  home$=escape$+"H"

40  move$=escape$+"Y"

(Lines 20...40 use the technique described in Section 5.2.2 to combine ESC with the next character in the escape sequence.)

With these defined, you could then use the following set of PRINT statements to clear the screen, write the title 'Screen control' and move to line 15, column 22:

110 PRINT clear$;home$;

120 PRINT "Screen control"

130 lineno = 15

140 column = 22

150 PRINT move$;CHR$(lineno+32);CHR$(column+32);

160 PRINT "New text position";

Finding out the control codes and expressing them in PRINT statements is essentially all there is to screen control. However, there are a couple of further complications to do with the way BASIC works.

The first concerns the code with the internal value 9. This is normally treated by BASIC as a TAB character and automatically replaced by the spaces required to take the cursor to the next tab position. Whenever you need to use this special character as a control code or as part of an escape sequence, you should first use the command OPTION NOT TAB to suppress tab expansion. (If you want to use tab expansion again later in the program, you an re-enable it with the command OPTION TAB.)

The second concerns the way BASIC automatically starts a new line when you read the righthand side of the screen. It does this by inserting a couple of control codes (CR and LF) in the list of print items. It is possible to insert these extra codes in such a way that your escape sequences no longer have the desired effect.

The solution to this problem is to precede your group of control codes by a program line containing the instruction WIDTH 255, which turns off the automatic next-line feature. If, at the end of the sequence of codes, you want to restore this feature, you can do this with the instruction WIDTH n, where n is the number of character positions across the screen. When you run BASIC on the Spectrum, the screen is 51 characters wide and so the instruction you will need is WIDTH 51.

Thus the complete set of instructions might be:

10  escape$=CHR$(27)

20  clear$=escape$+"E"

30  home$=escape$+"H"

40  move$=escape$+"Y"

100 OPTION NOT TAB : WIDTH 255

110 PRINT clear$;home$;

120 PRINT "Screen control"

130 lineno = 15

140 column = 22

150 PRINT move$;CHR$(lineno+32);CHR$(column+32);

160 PRINT "New text position";

170 OPTION TAB : WIDTH 51

Note: The series of codes move$;CHR$(line+32);CHR$(column+32) can be very neatly packaged into a user-defined function. With such a function set up, you could move anywhere on the screen simply by quoting the line number and the column number. Details of user-defined functions are given in Chapter 4.

3.5.2 Controlling the printer

The codes used to control a printer, like those used to control the screen, are either single Control codes with internal values in the range 0...31 or Escape sequences starting with the special 'ESC' character (internal value 27).

You use these codes for two main types of task:

The codes you use depend on the printer you want to control - not on the computer you are using. So the place to look for details of these codes is in the printer's own manual.

A number of printers use the same control sequences: such printers are described as being compatible. For example, there are a number of daisy-wheel printers that use the same codes as a Diablo 630, while some dot-matrix printers use the same codes as the Epson FX-80. However, these 'standard' codes only control a fairly limited range of printer features, so the printer manufacturer will normally have included additional 'non-standard' codes so that you can use all the printer's facilities.

The control sequences are sent to the printer by inserting them in LPRINT statements. The codes in each control sequence are expressed either as "character" (when they are associated with printable characters like Y) or as CHR$(value) where value is the code's internal value - just as they are when you are using codes to control the screen. Indeed, the whole process of converting the printer's codes into LPRINT statements can be directly modelled on the statements used above to control the screen.

Again, you need to remember to use OPTION NOT TAB to stop any characters with internal value 9 from being interpreted as a tab and expanded into a number of spaces, and you should use WIDTH LPRINT 255 to stop BASIC's automatic new-line feature affecting printer escape sequences. (Again, restore the new-line feature by using another WIDTH LPRINT command which resets the width of the printer.)

Often, the most difficult part of controlling the printer is in fact in working out the details of the codes you require. So as our example of LPRINT statements containing printer control sequences, we will use first the Diablo 630 codes and then the FX-80 codes that you would use to set Character pitch 12 (ie. 12 characters per inch along a line) and Line pitch 8 (ie. lines 1/8" apart down the page) because these are a couple of the more difficult codes to work out.

Example of using Diablo-630 codes

To set the Character Pitch on a Diablo 630 printer you have to set the distance the printhead moves between printing one character and the next. This distance is defined by the Horizontal Movement Index (HMI): if the HMI is n, then the distance moved is n/120 inches. Similarly, to set the Line pitch, you have to set the Vertical movement index (VMI): if the VMI is n, then the distance between one line and the next is n/48 inches.

To use Character pitch 12 and Line Pitch 8, you therefore have to set an HMI of 10 (so that the distance moved is 10/120 = 1/12") amd a VMI of 6 (so that the distance moved is 6/48 = 1/8").

The escape sequence that sets the HMI to 10 is ESC US 11 (note how the number in the code is one more than the HMI you want to set) and the escape sequence that sets the VMI to 6 is ESC RS 7 (again, note how the number in the code is one more than the VMI you want to set).

ESC is the familiar character with internal value 127; US and RS are the names of two more special characters (internal values 31 and 30, respectively); and the numbers are internal values. So you could set Character Pitch 12 and Line Pitch 8 with the following statements:

escape$=CHR$(27) : us$=CHR$(31) : rs$=CHR$(30)

hmi=10 : vmi=6

LPRINT escape$;us$;CHR$(hmi+1);: REM set Character Pitch 12

LPRINT escape$;rs$;CHR$(vmi+1);: REM set Line Pitch 8

Example of using FX-80 codes

The FX-80 printer uses individual escape sequences to set the different Character pitches and Line pitches. For example, the code that sets Character Pitch 12 is ESC M, whereas both the special character SI and the sequence ESC SI can be used to set Character Pitch 17. Similarly, the codes used to set Line Pitch 6 and Line Pitch 8 are ESC 2 and ESC 0, respectively.

The complication here is that, because 10 Pitch is the initial pitch used on these machines, the code used to set this pitch depends on the pitch that you are using at present. For example, if you are returning to 10 Pitch from 12 Pitch, the code to use is ESC P, but if you are returning to 10 Pitch from 17 Pitch, the code to use is the special character DC2.

The codes used to set Character Pitch 12 and Line Pitch 8 are ESC M and ESC 0 (zero), respectively. So you could use following statements to set this combination:

escape$=CHR$(27)

LPRINT escape$;"M"; : REM set Character Pitch 12

LPRINT escape$;"O"; : REM set Line Pitch 8

The technique of using PRINT commands to control the screen and the printer described here isn't used by every version of BASIC you might use. Many have specific commands to carry out simple actions such as clearing the screen or positioning the cursor. These are simpler to use than control sequences but the advantage of the technique used in Mallard BASIC is that the same technique is used whatever action you require. Moreover, special screen and printer commands tend to be only suitable when you are using a particular computer, whereas Mallard BASIC can be used to control the screen and the printer on a very wide range of computer systems.


Previous chapter Index Next chapter