Bottom     Previous     Contents

CHAPTER 4
Driving Graphics

Nil posse creari de nilo
[Nothing can be created from nothing]

Lucrecius, Be rerun Natura

We have already encountered the control of graphics by use of VDU commands (the VDU 'drivers') -- it is impossible to ignore them. What is the function of a VDU command? Hold down the CTRL key and press the G key: you beep. Enter

PRINT CHR$(7);

and you beep, appearing on the next line. When you enter

PRINT CHR$(7)

(ie without the semicolon) you beep and appear on the next line but one. To enter

VDU 7

again produces a beep, this time you are on the next fine. Entering

VDU 7:VDU 7

produces two beeps, as does

VDU 7,7

This simple example shows that the VDU command is a special kind of PRINT command -- it is a PRINT CHR$ command, where the command is terminated by a semicolon.

Asking about ASCII

The inquiring mind might then ask: 'Why is the PRINT CHR$ combination worth all this trouble?'. As you ask, it's ASCII, the American Standard Code for Information Interchange. Go into any mode other than 7, turn to the UG page 490, and then

VDU 66,111,114,105,115

to which the response is, how I love it,

Boris

and to reajly get it correct

VDU 66,1 11,1 14,105,105,1 15,32,65,108,108,97,110

- try it out to see what is produced, gorgeous really.
The numbers which follow a VDU command are the numbers which correspond to the characters in the ASCII. The command to produce Boris could be written

VDU 66 : VDU 111 : VDU 114 : VDU 105 : VDU 115

that is, as a series of commands -- each with one number following -- but it is simpler to run the numbers together if at all possible. If you look at the codes on page 490 of the UG, you will see that from ASCII 0 to ASCII 31 there are no characters. These are the control characters, which include the beep at 7 (the diagram says "beep").
Now turn to page 378 of the UG, where there is a summary of the meaning of the VDU codes from 0 to 31, plus code 127, There is an exact correspondence between the two summaries (ie the VDU on page 378, and the ASCII on page 490).
The original ASCII codes only extended from 0 to 127, as there were only seven holes on paper tapes plus one hole as a 'parity' check (ie count up the holes and punch an extra hole depending on whether the result was odd or even). This is why the meaning of codes 128 to 255 is "undersigned initially" as the UG says. Codes 0 to 31 were given names, and suggestions for standard meanings, but these codes were 'control' codes and partly depended on the system.
If you refer to the VDU code summary on page 378 of the U G, you will see a third column which is labelled CTRL: the keys in this column -- when pressed at the same time as the CTRL -- produce the ASCII control code given as the ASCII abbreviation (column four). The ASCII code 0 (for example) is abbreviated to NUL, and means 'null' do nothing; according to the last column of the figure on page 490, for the BBC computer it means 'does nothing'.
Looking at the ASCII abbreviations can explain some strange pairings. To disable the VDU is code 21 (CTRL U) which is abbreviated to NAK (Negative Acknowledge); whereas to activate the VDU one uses code 6 (CTRL F), or ACK (Acknowledge). Each computer has its own conventions for some codes but usually code 13 (&0D) is return and code 10 (&0A) is line feed, and so on. Otherwise the computer would have trouble talking to devices in languages they could understand.

Character generation

We have been talking about characters, and the characters from 128 to 255 are undefined in the figure on page 491 of the UG. However, unless a special operating system (OS) command is given -- of which more later -- only characters with codes 224 and 255 can be redesigned. To store each user-designed shape takes up 8 bytes, and so to store 128 shapes is 8*128 bytes or 1K -- to redesign the 32 shapes from 224 to 255 takes only 256 bytes.
Memory is again a problem.
The first thing we now have to find out is how to define a character, and details are given in the UG on pages 170-171, 384-385, and 389. The VDU command to design a character is 23 (&17), which is CTRL W (ETB -- End transmission block). On page 378 of the UG we discover that, once the system has been alerted by VDU 23, it expects 9 bytes to follow: the first byte gives the number of the character to be redesigned; and tht$remaining 8 bytes described what is the shape of the new character.
The work comes in working out the description of the shape, as Lucrecius said 'Yer can't get owt from nowt'. The routines I have developed are designed to make the design the important element, with the description of the design being made as simple as possible. These are the Character Routines 1.

First: how to design a character. In Figure 4.1, I show the ubiquitous man from the UG (page 170)

Figure 4.1 The Little Man

THE SHAPE THE BINARY THE DECIMAL
. . . *** . . 00011100 28
. . . *** . . 00011100 28
. . . . * . . . 00001000 8
. ******* 01111111 127
. . . . * . . . 00001000 8
. . . * . *. . 00010100 20
. . * . . . *. 00100010 34
. *. . . . . * 01000001 65

The numbers in the decimal column are the equivalents of the corresponding binary numbers, with the binary numbers being exactly related to the filled and empty squares in the character design.
The character number 224 is designed to be a 'man' shaped by the VDU command

VDU 23,240,28,28,8,127 ,8,20,34,65

which can be re-written in a more transparent form as

VDU 23 : REM Prepare to send a character definition
VDU 240 : REM and the character number is ASCII 240
VDU 28 : REM This is the top line defined
VDU 28,8,127 ,8,20,34 : REM the middle six
VDU 65 : REM and the last line

If you think back to the discussion of screen resolution, the 8*8 character frame is no newcomer. Note: as long as the VDU numbers are in the correct order, then the bytes can be split over more than one VDU statement. The most tedious part (and least artistic) is the conversion from binary numbers to decimal equivalents.

Character designer

The first of the Character Routines is PROC.--INB YTES, and this routine allows the user speedy and easy input of binary numbers to be converted into decimal equivalents. These converted numbers are then made available for other routines, to be assigned to character numbers, and to be manipulated.
The PROC_INBYTES routine uses two highly useful BB facilities (both related): byte vectors, and string indirection (UG pages 409-413). The local variable BYTES is set equal to &D00 (the start of a patch of memory available for user supplied resident routines, UG page 501), and the local variable STORE is set equal to 8 more than BYTES. BYTES will be used to hold the values of 8 bytes, and STORE will be used to store 8 characters which will be then converted into byte values.
There are two loops: one -- with index I -- corresponds to the fines in the character design; and the other -- with index J -- builds up the BYTES value for line I. Consider (Figure 4.2) what happens for line 5, once we have input the binary number 00001000 into the string $STORE which starts eight bytes on from the start of BYTES.
We now have to relate this arrangement to the routine PROC-- INBYTES. When the user types in 00001000, after the request LINE 4?, the eight characters are stored in the string starting at &0D08 (ie by $STORE). $STORE is not the same as STORE$.
STORE$ is a string variable, and $STORE points to a series oflocations starting at STORE, where the locations are filled with the ASCII codes of the characters. We know where $STORE ends because that location contains 13 (&0D). The ASCII code for 0 is 48, and the ASCII code for 1 is 49 (see page 490 of the UG), and so if the values of the locations from &0D08 to &ODOF are examined (48,48,48,48,49,48,48,48) then the correspondence with the input line 00001000 is clear to see. Note that location &0D10 contains 13, to indicate the end of the string.
Thus far we will presume that the correct values have been input to the first four locations of BYTES, and now the next problem is to input the correct value into the fifth location (BYTES?4). BYTES?I is first set to zero (note that I = 4), and in the J loop the value of BYTES?I is doubled. As the loop counter J is increased from 0 to 7 a check is made to see if that element of STORE (ie STOREAJ) is equal to the ASCII value of I (ie STORE?J = 49). If there is a I in the input number at this place in the order, 1 is added to BYTES?I.

Figure 4.2 Indirection Example

LOCATION NAME VALUE
&0D00 3584 BYTES?0 28
&0D01 3585 BYTES?1 28
&0D02 3586 BYTES?2 8
&0D03 3587 BYTES?3 127
&0D04 3588 BYTES?4 UNDEFINED
&0D05 3589 BYTES?5 UNDEFINED
&0D06 3590 BYTES?6 UNDEFINED
&0D07 3591 BYTES?7 UNDEFINED
&0D08 3592 STORE?0 48
&0D09 3593 STORE?1 48
&0D0A 3594 STORE?2 48
&0D0B 3595 STORE?3 48
&0D0C 3596 STORE?4 49
&0D0D 3597 STORE?5 48
&0D0E 3598 STORE?6 48
&0D0F 3599 STORE?7 48
&0D10 3600 STORE?8 13 (String end)

A binary number is formed in BYTES? I, and at the end of the routine the numbers needed to send the shape to a character code are in the eight bytes from &0D00.

Numbers to characters

To set up a byte vector of eight elements all that is needed is a

DIM VV 7

statement in direct/immediate mode. This declaration will remain operative until the computer resets the BASIC variables (eg some errors, or a new fine is entered into a program).
If we want to assign the character definition stored away at &0D00, then PROC_CHRASN is used. The parameters of the routine are the character number (I) and an eight element byte vector (NEWB) -- this byte vector is used as a temporary storage medium for the design numbers (used in some later routines.) All the routine does is simply send the numbers stored from &0D00 to the character I, by

VDU 23,I

and then eight

VDU BYTES?I

statements (storing in NEWB?I at the same time).
So we design the shape, and then input the binary numbers (by use of PROC_INBYTES): next we decide where to send the design, ie to which character, by use of PROC_CHRASN. Suppose we turn the design upside down, making line 0 into line 7? (but still keeping the left column on the left). We use PROC_CHREV.
PROC_REV is essentially PROC_ASN, but backwards. It starts with BYTES?7 and goes to BYTES?0, instead of vice versa. The new reversed design is stored in the byte vector NEWB.
Next there is a routine to flip from left to right (and right to left): PROC_CHRFLIP. This routine follows the standard pattern, but some points are of special interest. J and L% are loop counters, J for the line, and L% for the column within the line (and bit within the binary number). L% is an integer variable because FL% then gives an exact result. The new number for the line is compiled in K, in the same manner as the number was compiled from STORE in PROC_INBYTES. The IF statement is replaced by a logical assignment in PROC_CHRFLIP.
The logical assignment

K = K - ((2^L% AND BYTES?J) = 2^L%)

works to see if there is a I in the binary number BYTES? J in L%th position from the right, taking into account the binary number 2^L% will have a 1 in the L%th bit from the right.
If the number in BYTES?J has a 1 in L%th position, then (2^FL% AND BYTES?J) -- the logical AND, UG pages 205-206 -- will be a binary number with a bit equal to 1 in L%th position (with zeros elsewhere). A number with a 1 in L%th position and zeros elsewhere is equal to 2^L%. jfhe numerical value which results when the logical comparisons are TRUE is -1, and it is 0 when FALSE (UG 257-258, 369). So the value is subtracted to make -1 into +1.
Two examples using the number 28 (ie BYTES?J) and the position 0 and 3 (ie L%=0 and L%=3). First example: L% = 0 so that 2^L% is 1 (or 00000001 in binary), BYTES?J = 28 (ie 00011100 in binary); 2^L% AND BYTES?J is 00000000, which does not equal 2^L%. Second example: when L% = 3 then 2^% = 8 which is 00001000 in binary; 2^L% AND BYTES%} is 00001000 which is equal to 2^L%.
PROC_CHROT takes it all that bit further, it turns upside down and flips, that is, it rotates. The routine is like PROC_CHREV with PROC_CHRFLIP inside.
We have with all these routines the spare byte vector (usually called parameter NEWB). PROC_CHRSEND sends the content of a byte vector to the eight locations from &0D00. This allows more than one design to be examined and manipulated at any one time. The bytes from &0D00 are almost the equivalent of the accumulator in a microprocessor. The accumulator is where most of the actual computations are performed -- especially with the BBC computer's 6502 microprocessor.
The routine PROC_CHRINV inverts the design, ie all zeros become ones while all ones become zeros. If the result of the inversion is then sent back to &0D00, all the manipulation of the design is then available for the inverse design. Finally, PROC--INDEC is just like PROC_INBYTES, only you can enter decimal numbers.

Saving the routines

These routines are quite useful, and so it is sensible to try to incorporate them in any program which uses user-designed shapes. You may have noticed that the line numbers for the routines are very high, higher than any program will ever need: this is so that they can be loaded quite safely with any normal program.
If, however, the routines are saved as a BB program, then to load them by, say,

LOAD "SHAPES"

will erase the program already in the memory. We need to load the program fines in some different way. As we could enter the lines by typing them in, why do we not get the„ computer to type them in?
Type NEW and then enter the routines, checking to see they work -- and how do you check to see they work? You use a VDU command. To see if the design for character 224 is what you want, you enter

VDU 224

which prints out the character corresponding to the ASCII value 224. LIST to check that all is well.
The next stage is to send the contents of the program to a file: but not as a program file. When we type in at the keyboard we enter a character at a time, or do we? We actually send a value when the key is depressed, that value is then interpreted to appear as a character on the screen, or elsewhere. The value sent by the key depression is an ASCII value -- to read in ASCII values, therefore, does not clear out the BASIC program. We seem to require a file which has the program stored as ASCII values -- sometimes called an ASCII file.
To send the program to an ASCII file we use *SPOOL (U G page 402-403). To save the ASCII version of the routines we enter

*SPOOL "SHAPES"

and switch on the tape recorder to record, though with disks we do not have to bother.

LIST

which lists out the program, and sends it in ASCII form to the file SHAPES. To terminate we enter

*SPOOL

and the file closes.
To load the routines on top of a program we use

*EXEC "SHAPES"

and the *EXEC command acts as if the information coming from the file (in ASCII form) were being typed in at the keyboard. This means that it is possible to use these routines in any program. It would be even simpler still to have one or more sets of designed characters which one could pick and choose, depending on the program.

Saving the designs

The user designed shapes are located between &0D00 and &()DFF unless the characters are 'exploded', (U G page 427). Exploding means that more than the 32 characters (ASCII codes 224 to 255) are available to be redesigned: the ASCII codes from 32 to 255 (ie &20 to &FF) can be redesigned. The exploding of the memory allocation uses the command *FX 20,1, but the allocation of memory over-runs the beginning of BASIC (usually at &0E00, ie PAGE -- UG page 414).
The sequence to explode the memory for a full set of user designed characters is (all in immediate mode)

PRINT PAGE : PAGE = PAGE + &600 : *FX 20,1

This explosion must occur before the BASIC program is entered. The designs occupy memory from locations &()C00 to &CFF, and &()E00 to &0A00 if all characters are to be redesigned. The gap at &0D00 to &0DFF (which I used for temporary storage) is left for user-defined routines (see UG page 501 -- though this conflicts with page 502). Personally I think that 32 characters are sufficient for most purposes, so I will only consider the default 'imploded' 32 characters.
To save the user-designed characters, therefore, enter

*SAVE "CHARS" 0C00 0CFF

and then to load these new designs into any program all that is needed is Chapter 4 Driving Graphics *LOAD "CHARS" The extension to explosion is simple enough, That the characters occupy fixed positions also means that the first location of any character is at (CHRLOCN - 224)*8 + &0C00. The character routines can easily be modified to take this arrangement of characters and locations into account.

Text in graphics

All Icons in this book are printer dumps taken directly from the screen, including the Icon label and any other labels (eg Hypotenuse). The labels were printed and positioned by a routine called PROC_PRINT, which comes in two versions. The first version

1000 DEF PROC_PRINT(A$,X,Y,CLR)
1010 VDU 5 : REM Write text at graphics cursor
1020 GCOL 0,CLR : REM Set text colour
1030 MOVE X,Y : REM Move to X,Y without plotting
1040 PRINT A$ : REM Write the text
1050 VDU 4 : REM Set text back to text cursor
1060 ENDPROC : REM PRINT Version 1

uses only two VDU commands. However, GCOL and MOVE can be expressed as VDU commands (refer to page 378 of the UG). The second version uses many more VDU commands, to the same effect.

1000 DEF PROC_PRINT(A$,X,Y,CLR)
1010 VDU 5,18,0,CLR, 25,0,X;Y;
1020 PRINT A$ : VDU4
1030 ENDPROC : REM PRINT Version 2

I am ambivalent about the second routine. The first routine is clear, explicit and well documented. The second routine is only clear to those who wish to find out, but it has a coherence -- due to the use of the VDU commands as a long fine. The second routine is, I suspect, rather more computationally efficient.
Corresponding to GCOL there is VDU 18 (U Gpage 381-382) with the same parameters (ie 0 and CLR), both of which are of one byte (the comma ","). Move X,Y is the same as PLOT 4,X,Y and (page 386) this is the same as VDU 25 with the equivalent parameters. The first parameter is one byte (","), and the other two parameters are two bytes (";").
On balance, I prefer the second version.

30000REM-------------------------------

30010

30020

30030REM G R A P H I C ART

30040

30050REM (c) Boris Allen, 1983

30060

30070

30080REM-------------------------------

30090

30100REM Character Routines 1

30110

30120REM-------------------------------

30130

30140 DEF PROC_INBYTES

30150 LOCAL I,J,STORE,BYTES

30160 BYTES = D00 : STORE = BYTES + 8

30170 FOR I = 0 TO 7

30180 PRINT "LINE ";I;

30190 INPUT $STORE

30200 BYTES?I = 0 : FOR J = 0 TO 7

30210 BYTES?I = BYTES?I*2 : IF STORE?J=4

9 THEN BYTES?I = BYTES?I + 1

30220 NEXT J : NEXT I

30230 ENDPROC : REM INBYTES

30240

30250 DEF PROC_CHRASN(I,NEWB)

30260 LOCAL J,BYTES : BYTES = D00

30270 VDU 23, I : FOR J = 0 TO 7

30280 NEWB?J = BYTES?J : VDU BYTES?J : N

EXT J

30290 ENDPROC : CHRASN

30300

30310 DEF PROC_CHREV(I,NEWB)

30320 LOCAL J,BYTES : BYTES = D00

30330 VDU 23,I : FOR J = 7 TO 0 STEP -1

30340 NEWB?(7-J) = BYTES?J : VDU BYTES?J

: NEXT J

30350 ENDPROC : REM CHREV

30360

30370 DEF PROC_CHRFLIP(I,NEWB)

30380 LOCAL J,K,L%,BYTES : BYTES = D00

30390 VDU 23,I : FOR J = 0 TO 7

30400 K = 0 : FOR L% = 0 TO 7

30410 K= K*2 : K = K - ((2^L% AND BYTES?

J) = 2^L%) : NEXT L%

30420 NEWB?J = K : VDU K : NEXT J

30430 ENDPROC : REM CHRFLIP

30440

30450 DEF PROC_CHRROT(I,NEWB)

30460 LOCAL J,K,L%,BYTES : BYTES = D00

30470 VDU 23,I : FOR J = 7 TO 0 STEP -1

30480 K = 0 : FOR L% = 0 TO 7

30490 K = K*2 : K = K - ((2^L% AND BYTES

?J) = 2^L%) : NEXT L%

30500 NEWB?J = K : VDU K : NEXT J

30510 ENDPROC : REM CHROT

30520

30530 DEF PROC_CHRSEND(NEWB)

30540 LOCAL I,BYTES : BYES = D00

30550 FOR I= T0 O 7

30560 BYTES?I = NEWB?I : NEXT I

30570 ENDPROC : REM CHRSEND

30580

30590 DEF PROC_CHRINV(I,NEWB)

30600 LOCAL J, BYTES : BYTES = D00

30610 VDU 23,I : FOR J = 0 TO 7

30620 NEWB?J = 255 - BYTES?J : VDU NEWB?

J : NEXT J

30630 ENDPROC : REM CHRINV

30640

30650 DEF PROC_INDEC

30660 LOCAL I, NUM,BYTES : BYTES = D00

30670 FOR I = 0 TO 7

30680 PRINT "LINE ";I;

30690 INPUT NUM : BYTES?I = NUM

30700 NEXT I

30710 ENDPROC : REM INDEC



Next     Top