+ All Categories
Home > Documents > The Alien, Numbereater and other Programs for Personal Computers

The Alien, Numbereater and other Programs for Personal Computers

Date post: 11-Sep-2021
Category:
Upload: others
View: 2 times
Download: 0 times
Share this document with a friend
90
Transcript
Page 1: The Alien, Numbereater and other Programs for Personal Computers
Page 2: The Alien, Numbereater and other Programs for Personal Computers

The Alien, Numbereater and other Programs

for Personal Computers

Page 3: The Alien, Numbereater and other Programs for Personal Computers

Some other computing books from the Macmillan Education Press

Beginning BASIC P. E. Gosling

Continuing BASIC P. E. Gosling

Microprocessors and Microcomputers Eric Huggins

A Practical Introduction to Pascal I. R. Wilson and A. M. Addyman

Page 4: The Alien, Numbereater and other Programs for Personal Computers

The Alien, Numbereater and other Programs for

Personal Computers

John Race Senior Lecturer,

Department of Computer Science Brunei University

Page 5: The Alien, Numbereater and other Programs for Personal Computers

©JohnRace 1981

All rights reserved. No part of this publication may be reproduced or transmitted, in any form or by any means, without permission

Firstpublishedl981 by THE MACMILLAN PRESS LTD London and Basingstoke Associated companies in Delhi Dublin Hong Kong Johannesburg Lagos Melbourne New York Singapore and Tokyo

The paperback edition of this book is sold subject to the condition that it shall not, by way of trade or otherwise, be lent, resold, hired out, or otherwise circulated without the publisher's prior consent in any form of binding or cover other than that in which it is published and without a similar condition including this condition being imposed on the subsequent purchaser

ISBN 978-0-333-28079-9 ISBN 978-1-349-05175-5 (eBook)DOI 10.1007/978-1-349-05175-5

Page 6: The Alien, Numbereater and other Programs for Personal Computers

Contents

Introduction Palindromes Brackets Orbit Pascal's Triangle Superlife Double Density Histograms Spiral Program Remover Prime? Playfair The Alien General Input Program Clear Numbereater Appendix: Amendments to Programs for later PETs

with revised ROMs Index

2 4 8

13 25 26 39 45 50 61 62 67 71 75 77

80 83

Page 7: The Alien, Numbereater and other Programs for Personal Computers

Introduction From the age of sixteen we can own machines which are physically much more powerful than we are. A Victorian engine driver would have been quite shocked and alarmed at the way young people can now afford to buy, and are allowed to drive, weird conveyances that are more convenient and efficient than his. His engine was far too complex and expensive for amateurs. To some extent, the older generation of computer users look upon the mass market in home computers with the same shocked alarm as the Victorian engine driver would have viewed the moped - even to the extent of denying that the new machine was a real vehicle at all.

But now you can buy, and you should buy as soon as you can, a machine - the home computer - which can outperform some of your mental muscles, as the moped outperforms your leg muscles. Of course you are its super­ior in many other ways. Unlike it, you can reason by analogy, think laterally, use your imagination. But it can calculate quickly and prec­isely, follow specified procedures without deviating by accident or imp­ulse, and write and draw tirelessly. A teacher of the old school would certainly call such abilities mental ones, and award high marks to pupils who showed them. What is happening is that, just as we had to learn to accept, and then enjoy, the fact that machines could be strong­er than us, so we must now learn how to accept and enjoy the intellect­ual powers of computers, which in general happily complement our own.

Again, the motorbike overcomes air resistance and friction more spect­acularly than we can, but needs us to balance it and direct it. Simil­arly a computer will beat us in following defined logical and mathematic­al procedures, but needs us to set the problem and define the proced­ures. Then, together, we can set off and travel far afield for enjoy­ment and profit.

This book is for people - students, teachers, or just enthusiasts, at home or in school or college. It is intended as a source of ideas for projects, a series of examples of techniques and applications, and a comfort to those who, like the author, write programs too often that do not work properly, and who then brood over our shortcomings and try to discover more reliable methods for converting our brilliant ideas into sound computer systems.

Those who run courses in schools or colleges may find the book useful as a source of worked examples and as recommended background reading, part­icularly if the course revolves round the use of BASIC. The programs in the book are mostly in BASIC, and in particular, Commodore PET BASIC. However, although the computer used is the small PET 2001 8K, the pro­grams should be transportable with more or less amendment to other BASIC computers such as APPLE, TANDY, SORCEROR, or the British NASCOM, ACORN, or SINCLAIR computers. There are also examples of Assembler and machine code programs for the MCS 6502 microprocessor, used on PET, APPLE 2, and many other systems. If you have a PET with a cassette deck 1 you can buy a cassette containing the programs in this book, by contacting me via Uacmillan Press.

There are a number of programs in this book which require additional equ­ipment to work: a printer, an interface for a relay to drive an electric light, a second cassette. These may be useful to those who are consider­ing extending their,system.

BASIC is a language which provokes as much antagonism among professional computer people as the home computer itself. Indeed, it is a language in which it is very easy to write programs that will run, but quite hard to write programs that will run correctly. Since the professionals are rightly concerned about high standards - computers today may kill people in hospitals or aircraft or trains if they fail - they fear that

2

Page 8: The Alien, Numbereater and other Programs for Personal Computers

sloppy habits learned from BASIC could become engrained and pernicious. They would prefer the enthusiast to be introduced to computers through a more rigorous language, like PASCAL, in which control structures are safer and programs are rejected by the rules before they try to run on real data. Unfortunately BASIC has become almost a standard language for micros, and variants are used even for process control and business applications ( PLANCOL, DEC BASIC-PLUS).

But with self-discipline it is possible to write reasonably clear, fast, safe BASIC programs and - although the author does not always take his own medicine - some tips for overcoming the bad features of BASIC are given in the pages that follow.

If you find any mistakes in the programs, do let me know: but I do not accept any responsibility for anyone using a program and coming to grief whether he is an astronaut who falls into the sun by relying on ORBIT or a spy who uses PLAYFAIR and has his transmissions decoded by counter­intelligence.

My thanks are due to John Watson of Macmillan for encouraging the writ­ing of this book, to my son Tom for his program for finding the first sequence of 20 non-primes, which he really did write to do his maths prep in the 3rd form of Abingdon School: to my Brunel student Harminder Ghattaura who wrote a 6502 program to draw double precison lines on the PET screen using Bresenham's algorithm, and to my wife and daughters who had to put up with the noise of a golfball clattering away in the back­ground.

It was my father's interest in codes and cyphers that prompted the pro­gram PLAYFAIR.

"Inte rface box" controlling e lectric l ight, Commodore PET 2001 BK, second casse tte p layer, Acula b printer controller , IBM p r inte r.

3

Page 9: The Alien, Numbereater and other Programs for Personal Computers

Palindromes Techniques: recursion, string handling, demonstration cheating.

Try running the program, and check the results with Figure 1. Adam's famous remark to Eve, Madam I'm Adam, is a palindrome - i.e. it reads the same backwards as forwards, if one ignores the spaces between the words and sticks to upper case. XYZ obviously is not a palindrome. The last trial refers to Ferdinand de Lesseps, whose palindrome is more successful than his attempt to build the Panama canal was. (Napoleon's 'Able was I ere I saw Elba' goes in nicely, spaces and all, since each word matches another of the same length on the other side of 'ere'}.

Now look at the program (Figure 2.}. The whole point of this program is to show how you can construct a program to match a formal definition of some data - in this case, a palindrome, which is defined in lines 5 -6, recursively: that is, the definition makes use of itself. ThiS may seem circular, and it would be, but for the fact that the definition uses itself on a shorter, simpler case - the possible-palindrome with its end characters removed - so that each recursion deals with a shorter string. Secondly the definition includes a terminating condition other than continued use of itself - the 'length 0 or 1' condition, which pro­vides an exit and prevents recursion ad infinitum.

In computer science recursion is quite useful, for example, to define a 'number': a number is a digit, or a digit concatenated (strung onto} a number. You may also like to see recursion at work in the opposite direction - constructing examples of recursively defined things, instead of checking things against a recursive definition, in 'Brackets'.

Back to the program. The sequence of instructions called Palindrome Tester is typical of this type of program. It is responsible for sett­ing up a stack of entries for holding the workings at each level of re­cursion: R$(} will contain the reply sent up by each lower-level invoc­ation of the Tester subroutine to the level above, and A$() will contain the string sent down by one level to the level below for checking. We clearly anticipate having palindrome-candidates of never more than 40 characters in length. Palindrome Tester also has to get the candidate palindrome from the experimenter and place it in A$(0}, the top level.

Palindrome Tester uses the Recursive Tester ( GOSUB 1000 } and prints the answer. Recursive Tester simply acts on the rules previously defin­ed: if the string it has in its A$(LEVEL} is empty or only one char­acter long it can return the answer YES to the routine which called upon it - either Palindrome Tester, if it is at level 0, or a higher level invocation of itself. Otherwise it has to check to see if it can reject the string on the grounds that its first and last characters are not the same ( line 1020 } •

Now at line 1030 Recursive Tester has to extract the middle part of the string given to it, using MID$(string,I,J} which returns a substring consisting of the J characters of 'string' starting at the Ith position. It then gets ready to go down by adding one to LEVEL and calls itself. On return from itself, it resets LEVEL and returns YES up\vards if and only if its lower-level examination also said YES.

As a matter of fact one could have simplified this program a bit since the descent into recursio~ is a straightforward one-way process - as soon as any level says NO we could leave the program instead of relig­iously carrying NO up and up. But in many more complicated examples of recursion, any one level cannot have the authority to decide unilateral­ly to terminate the whole process. For example, in chess, recursion can be used to find a player's best move, a.nd each level of the BESTHOVE

4

Page 10: The Alien, Numbereater and other Programs for Personal Computers

SEARCHING FOUND PALINDROME LOADING READY. RUN ? MADAMIMADAM MAD AMI MADAM

ADAMIMADA DAMIMAD

AMIMA MIM

I YES BREAK IN 60 READY. RUN ? XYZ XYZ NO RUN ? AMANAPLANACANALPANAMA AMANAPLANACANALPANAMA

MANAPLANACANALPANAM ANAPLANACANALPANA

NAPLANACANALPAN APLANACANALPA

PLANACANALP LANACANAL

ANACANA NACAN

ACA c

YES

Figure 1. Results of PALINDROMES program

2 REM------------ PALINDROMES----------3 REM 5 REM A PALINDROME IS A LETTER-STRING OF LENGTH 0 OR 1, 6 REM OR A PALINDROME WITH THE SAME LETTER ADDED TO EACH END. 8 REM 10 REM-------PALINDROME TESTER---------15 REM 20 DIM R$(40) ,A$(40) :REM STACKS 30 INPUT A$(0): REM CANDIDATE P/DROME 40 LEVEL=O: GOSUB 1000 50 PRINT R$(0): REM REPLY YES OR NO 60 STOP 997 REM 998 REM------- RECURSIVE TESTER 999 REM 1000 R$(LEVEL)="YES":REM ASSUME IT IS 1005 PRINT TAB(LEVEL); A$(LEVEL) 1010 IF LEN(A$(LEVEL))=<l THEN 1900 1020 IF LEFT$(A$(LEVEL),l)<>RIGHT$(A$(LEVEL),l) THEN 1890 1030 A$(LEVEL+l)=MID$(A$(LEVEL),2,LEN(A$(LEVEL))-2): REM EXTRACT MIDDLE 1040 LEVEL=LEVEL+l 1050 GOSUB 1000: REM IS MIDDLE A PALINDROME? 1060 LEVEL=LEVEL-1 1070 IF R$(LEVEL+l)="YES" THEN 1900 1890 R$(LEVEL)="NO" 1900 RETURN

Figure 2. PALINDROMES program

5

Page 11: The Alien, Numbereater and other Programs for Personal Computers

routine will call itself many times to find out what the opponent's BESTMOVE is in answer to one of many possible legal moves. If at this level BESTMOVE found a win or loss it could not come out from under the superstructure of higher level invocations of BESTMOVE and say 'resign' or 'claim a win': it has to return its small contribution of informat­ion to its elders and Letters.

Recursion has a beauty which fascinates the logician. Read 'Godel, Escher, Bach' by Douglas nofstadter, Harvester Press 1979 for an account of how this concept fits into recent thinking in mathematics, logic and computer science. Bertrand Russell pointed out the dangers of state­ments which refer to themselves, like 'This statement is false', but it has been found that this flaw, of self-reference, is engrained in any formal system to such an extent that it cannot be eliminated, and nmq we are learning to live with it, much as 17th century mathematicians were bedevilled with the problems of infinity implicit in series and the calculus.

Of course a computer has a finite store and if recursion is overdone on purpose or by accident ( if the algorithm has no terminating rule, or if the case under study is too complex ) , then the computer will show its finite nature by a message such as

TOO MANY NESTED SUBROUTINE CALLS

or OUT OF MEMORY

On a lighter note, see Figure 3. I needed to display typical results from the program, and a convenient way is to run it, then go down the screen shifting the output over to the right and putting in a line number, PRINT, and quotes, so by RUN 9000 one can simulate the operation of the real program. I did this to get round a problem of printing results, but it is a useful way of fiddling demonstrations when working with slow and error-prone equipment which might fail in front of an important visitor!

9000 PRINT" SEARCHING 9010 PRINT" FOUND PALINDROME 9020 PRINT" LOADING 9030 PRINT" READY. 9040 PRINT" RUN 9050 PRINT" ? MADAMIMADAM 9060 PRINT" MAD AMI MADAM 9070 PRINT" ADAMIMADA 9080 PRINT" DAMIMAD 9090 PRINT" AMIMA 9100 PRINT" MIM 9110 PRINT" I 9120 PRINT" YES 9130 PRINT" BREAK IN 60 9140 PRINT" READY.

Figure 3. How to cheat in demonstrations

6

Page 12: The Alien, Numbereater and other Programs for Personal Computers

9

( ( ( ( 3*6/ (2) ) ) ) )

75/(75)

(7494*2)

(81)/996-((6))/8343/7*831

(4)*(23772/8451-((6))*(948)*16-(152-4/4-(18))*((((336))))*(6)+8+9)/29616*2

(6f+5-(768488)-18

3816356

688

((6)/62418/83247513)*(2)

(481788-56)

4

< < < 4-5- < < < 8 8 > > » » (35432467) *371

97-585965

3

(35)-(((6)/659)/2*251859/176-8-72-(91/(3)*816-(9)+675)-581-785)/3+3*(8-((9* 7569/24+421))/(6))*(123)-((((4)*84))-379361*(83))+(22)

5

94391

698

7

3

Figure 4. Results of BRACKETS program

7

Page 13: The Alien, Numbereater and other Programs for Personal Computers

Brackets Techniques: recursive generation, string manipulation, random numbers, recovery from errors detected by the operating system.

Arithmetic expressions are often encountered as the right hand side of formulae, and computer compilers or interpreters need to be able to break them down into their components - numbers, which are composed of signs, digits, decimal points: arithmetic operators like + - * /: and brackets, to over-ride the normal precedence rules of the operators.

It is a useful exercise to write a program which actually composes such expressions - nonsensical, since the numbers and operators are chosen at random, but well-formed according to the rules for expressions, which in our case we will restrict to three:

A correctly formed expression consists of a number, or

an expression with brackets round it, or

two expressions concatenated together with an operator between.

Furthermore we will define a number as a digit, or a number concatenated with a digit: this gives us unsigned integers, of length 1 to infinity. To generate expressions we can invoke these rules at random, elaborating an expression indefinitely, but biasing the random selection so that on the whole expressions are short enough to avoid overflowing memory space in the computer. Try the program. You should see results similar to Figure 4. Eventually a n~~er will be produced so long, and/or the depth of expression will approach the maximum allowed, so you will get an OUT OF MEMORY or similar message. ( This appears not because memory is really exhausted, but a limit has been reached for GOSUB nesting, or for space to hold temporary variables.)

You can test that the expressions are in fact well-formed by pushing them over to the right with the Insert key and prefacing them with PRINT commands. If you press RETURN the PET will evaluate the expression in calculator mode. Incidentally, you may notice something odd about the distribution of digits in the nuro~ers. What is this, and why was the oddity not put right? ( I shall reveal the answer later.)

Now examine the program (Figure 5 ). It starts by reminding us of the rules chosen for expressions, and DIMensioning an array of CFE$() to use as a work-stack during the run. The program continually GOSUBs to 1000 and prints the result, prefacing it with an extra PRINT to separate multi-line expressions from one another.

In 1000 the program has to decide which rule to apply: it can obtain the expression as a number ( rule 1 ) or as a bracketed expression ( 2 which means going down a level, getting an expression, and bracketing it, or ( rule 3 ) as two expressions joined together with an operat-or, chosen at random, between them. ( F$ provides the operators to choose from). The method of choice is to take a random number in the range 0 to 1 and test it: if it is not more than .40, a number is produced to be the expression: otherwise the program knows that at least one lower­level expression will be required, so it goes to get it at line 1410, then it chooses ( line 1425 ) whether to surround this expression with brackets ( rule 2 ) or get another at line 1470 and join the two with an operator chosen randomly out ofF$ and temporarily put in G$ - rule 3.

It is interesting to work out what the chances are of generating expr­essions of different logical depths - especially to anticipate the poss-

8

Page 14: The Alien, Numbereater and other Programs for Personal Computers

5 REM--------BRACKETS-------------8 REM 10 REM A CORRECTLY FORMED EXPRESSION INCLUDING BRACKETS - A 'CFE' - CAN BE A 20 REM NUMBER, OR AN EXPRESSION IN BRACKETS, OR AN EXPRESSION CONCATENATED 40 REM WITH AN OPERATOR AND ANOTHER EXPRESSION, E.G.: 60 REM 70 REM 80 REM 90 REM 100 REM

123 (123) (123) +123

RULE 1 2 3

200 REM------GENERATE RANDOM VALID CFS 205 REM 207 DIM CFE$(50) :F$="+-*/" 210 LEVEL=O:GOSUB 1000 220 PRINT:PRINTCFE$(0) :GOTO 210 997 REM 998 REM-------- CFE GENERATOR 999 REM 1000 IF RND(5)>.40 THEN 1400 1009 REM 1010 REM-----RULE 1, CFE=NUMBER 1015 REM 1020 CFE$(LEVEL)="" 1025 CFE$(LEVEL)=CFE$(LEVEL)+RIGHT$(STR$(10*RND(5)),1) 1027 IF RND(5)<.6 THEN 1025 1030 RETURN: 1397 REM 1398 REM-----RULE 2 OR 3 1399 REM 1400 LEVEL=LEVEL+1 1410 GOSUB 1000 1420 LEVEL=LEVEL-1 1425 IF RND(5)>.50 THEN 1450 1427 REM 1428 REM-----RULE 2, CFE=(CFE) 1429 REM 1430 CFE$ (LEVEL)=" ("+CFE$ (LEVEL+1) +")": 1440 RETURN 1447 REM 1448 REM------RULE 3,CFE=CFE+OPERATOR+CFE 1449 REM 1450 CFE$(LEVEL)=CFE$(LEVEL+1) 1460 LEVEL=LEVEL+1 14 70 GOSUB 1000 1480 LEVEL=LEVEL-1 1485 G$=MID$(F$,INT(RND(5)*4+1),1)

REM RULE 2

1490 CFE$(LEVEL)=CFE$(LEVEL)+G$+CFE$(LEVEL+1): REM RULE 3 1500 RETURN

Figure 5. BRACKETS program

9

Page 15: The Alien, Numbereater and other Programs for Personal Computers

ibility of exceeding the PET's limit of 2S active GOSUBs. Clearly, at each level the descent will cease if the test at line 1000 fails, which it will in 40% of cases. But if this test takes us to 1400 we shall descend further, whether rule 2 or 3 is chosen, and possibly descend a second time if the test at 1142S leads us to rule 3. Each new descent may cause us to go yet deeper, but we can calculate the odds.

The decision tree at each level looks like

.40 rule 1 !

& back up

.SO rule 2 !

& back up

.60 rule 2 or 3

down one anyway

.SO rule 3

down one again and then back up

The probability of staying at the first level is obviouslv .40. So 40% of the time, expressions will consist simply of a number. However, 60% of the time the program will go down a level once, and possibly, by rule 3, twice. At these lower levels the program may simply come back with a (rule 1) number, 40% of the time, or go still deeper (rule 2/3 ), 60% of the time.

If the program is begun 1000 times therefore, 400 expressions will con­sist of a plain number, but in 600 cases plus a further 300 cases it be re-entered at a lower level. Out of these 900 cases, 40% will again terminate, but 810 (=900 x ( .6 x ( 1 + .S ))) entries will be made into yet lower levels by the program.

It follows that the expected number of entries to level 2S, given 1000 entries at level 0, will be

2S 1000 X .9 72

which explains why there is a fair frequency of occasions when an entry at level 0 ends up 'blowing the PET's GOSUB stack', with the slightly misleading OUT OF MEMORY error. In fact this error may occur before level 2S, when higher levels are invoked more than once each.

To find out if this analysis was correct I modified the original pro­gram to keep a count of the number of times the program was entered at each level, by altering line 1000 to read

FQ(LEVEL) = FQ(LEVEL) + 1: IF RND(S) > .40 THEN 1400

and DIMensioned an array FQ(2S) in 207. Two further modifications were needed - a short program to print the frequency counts FQ() and the cor­responding expected count ( .6 x the preceding one ) , see Figure 7., and a fix to keep the program running even when it encountered an OUT OF MEMORY error. This was achieved by loading the keyboard buffer with the instruction Go9998 return, so that as soon as BASIC left the program because of an error, it brumediately restarted after it encountered the re-entry instruction planted by the program itself. (Note that Go is an acceptable abbreviation for GOTO: in fact most BASIC commands can be shortened like this to the condensed 'token' form used internally.)

Finally, after letting the program run a fair time I stopped it and, after setting the output to appear on the printer by keying

10

Page 16: The Alien, Numbereater and other Programs for Personal Computers

208 POKE 525,7: FOR I=O TO 5 209 POKE 527+I, ASC(MID$("Go9998•,I+1,1)):NEXT :POKE 533,13

READY.

9998 PRINT "LEVEL ACTUAL PREDICTED• 9999 PRINT" RESULTS RESULTS" :PRINT 10000 Q=FV(O) :FOR I=O TO 25: PRINT I, FV(I), INT(Q):Q=Q*.9 :NE~~T 10010 GOTO 208

READY.

Figure 6. Modifications to BRACKETS program

LEVEL ACTUAL PREDICTED RESULTS RESULTS

0 566 566 1 508 509 2 437 458 3 3.87 412 4 338 371 5 314 334 6 280 300 7 246 270 8 219 243 9 206 219

10 195 197 11 179 177 12 177 159 13 149 143 14 133 129 15 108 116 16 99 104 17 68 94 18 49 84 19 37 76 20 23 68 21 15 61 22 10 55 23 7 50 24 0 45 25 0 40

Figure 7. How deep does it go?

11

Page 17: The Alien, Numbereater and other Programs for Personal Computers

OPEN 1,4: CMD 1

caused the program to be entered with a GOTO 9998 to display the counts. There appears to be satisfactory correlation between the run results and the numbers expected by calculating. For example

10 566 X .9 197.35 See Figure 7.

However, there is a lack of correlation as the depth increases, since there is an increasing chance that the program has to stop due to 'OUT OF MF~ORY' trouble, when that trial, which ought to have increased the count of deep recursions, has to be ditched.

Before leaving BRACKETS, I promised I would return to the actual numbers produced at line 1025, by the process of concatenating the last digit of a random number ( in the interval 0 - 1 ) to a the number produced so far. The chance of a single digit number is .4, of a two digit one, .6 x .4, of n digits

n • 4 * . 6

The sum of these probabilities is

0 1 S .4 X ( .6 + .6 + ••• )

whence

0 (S - .6S)/.4 .6

so s = 1 as it should.

But why do none of the numbers contain a digit 1? It is simply because the last digit of the random number's fractional decimal part, prepared for display by taking the STR$ of it, is bound to be non-zero - if it had been zero, it would have been a suppressed space. After I realised this I thought of using not the last, but the first digit after the decimal point, which would indeed contain zeros one tenth of the time. In the end I left the program as it was, thinking that at least by this procedure I reduced the chance of generating strings that would produce zero divide errors - although they might still occur as a result of, say 100/(73-73), and of course it was quite likely the expressions generated by BRACKETS could blow up through causing overflows or underflows in calculation, for example:

1234567890 X 1234567890 X •••••••••

Not that there is such a big likelihood of the results of BR~CKETS being evaluated in hot blood, unless they Nere used to test th~ effectiveness of an expression-analyser, such as an interpreter or compiler.

'BRACKETS' thus demonstrates how recursion can produce complex strings by applying simple rules which are invoked at many levels, in this case chosen by a random number generator. But it sometimes goes too far and tries to create strings too long to be handled by a real computer in the physical world: logically though, the program can produce indefinitely complex ones.

12

Page 18: The Alien, Numbereater and other Programs for Personal Computers

Techniques: simulation of body moving in gravitational field, use of keypad as controller.

Most computer game enthusiasts will have played with a Moon Lander pro­gram. This simulation however is concerned with the stage before that -the insertion into lunar orbit. Figures 8 - 10 show the guts of ORBIT.

In lines 10 - 30 we establish, in good 'top down' fashion, the structure of the program. Ironically, the subroutine at 3000 is not entered since when the routine GAME was written there seemed no point in coming out of it unless to stop as a result of running out of fuel, or getting too far from the moon.

In INIT ( Figure 9 ) the centre of the moon is made the origin of the x,y reference plane, and semi-mnemonic variables allocated. Again, MX and l~ turned out not to be useful and are never referred to again. (Yet if one regards a program as being not simply instructions to the comput­er, but equally aide-memoires to the programmer, I need not apologise for leaving them.) SX and SY are certainly needed: this assignment sets the spacecraft to the left and at the bottom of the field of view - the screen. Clearly the distance in miles will have to be converted to screen coordinates. I have made the decision to work in 'real world' units and convert to screen units when ready to plot. And, unrepentant, I use miles not kilometers since my mind recalls such values as 7 miles a second ( earth escape velocity ) and 32.2 feet per second per second ( acceleration due to gravity at earth's surface ) so much more easily than their metric equivalent. VX and VY are the two components of the spacecraft's velocity along each axis: it is travelling slowly but will gradually fall inwards to the moon faster and faster.

At line 1030 we set up KM, nothing to do with kilometers, but a constant related to the moon - its gravitational constant. We know that the moon's pull is about one sixth that of earth, which is 32.2 feet per sec per sec - or 32.2 divided by 6, then further divided by the number of feet in a mile. Why not, you may ask, work out the answer once and save getting the computer to do it again every time the program is run? The answer once again is that I do not care if I ask the computer to do some redundant calculations if it makes life only a little bit easier for me. Leaving the workings unresolved is a simple way of reminding myself of what I intended. The moon's gravitational acceleration is thus deriv­ed from earth's and converted to miles per sec per sec for compatibility with the units of measure used. The result is divided by the square of the moon's radius in miles. Why?

The gravitational 'pull' ( to use Newtonian thinking ) of a body is pro­portional to the square of the distance away, the body affected is: i.e.

2 a = k/d

Now we know that at the moon's surface, 1080 miles from its centre, a= 32.2/(6*1760*3), so we can find k by substitution, after which we have a formula by which we can calculate the acceleration at any distance

2 a= (32.2/(6*1760*3))/d

The expression in brackets we have just computed is KM, for use later in the simulation, for various d's.

At line 1040 we set the variable l4AP to zero. MAP will be used to note the current scale of the map shown on the screen. Whenever the subrout-

13

Page 19: The Alien, Numbereater and other Programs for Personal Computers

5 REM--------- ORBIT ----------------------10 GOSUB 1000: REM !NIT 20 GOSUB 2000: REM GAME 30 GOSUB 3000: REM FINISH 40 STOP

Figure 8. Main logic of ORBIT program

997 REM 998 REM ------ !NIT 999 REM 1000 MX=O:MY=O: 1010 SX=-20000:SY=-12000: 1020 VX=.Ol:VY=.Ol: 1030 KM=(32.2/(6*1760*3))*1080A2: 1040 l-1AP=O:POKE 59468,12: 1050 FOR V=lT09:READ RX(V),RY(V) :NEXT: 1060 DATA -1,-1, 0,-1,· 1,-1 1070 DATA -1, 0, 0, 0, 1, 0 1080 DATA -1, 1, 0, 1, 1, 1 1090 Ll=l:E=9: 1900 RETURN

REM MOON COORDS REM S/CRAFT POS REL TO MOON REM MILES PER SEC INITIALLY REM CONSTANT FOR MOON PULL REM FORCE MAP, SET GRAPHICS REM LOAD BURN VECTOR TABLE

REM OLD S/CRAFT POS, FUEL LEFT

Figure 9. Initialisation section

1997 REM 1998 REM-------- GAME 1999 REM 2000 D2=SX*SX+SY*SY: D=INT (SQR(D2)): REM l-100N-s;'CRAFT DISTANCE SQD 2010 A=KM/D2:P=.005: REM ACCELERATION DUE MOON,MAP 2020 IF(ABS(SX)>20000)0R(ABS(SY)>l2000)THEN 2300: REM LOST 2070 IF(ABS(SX)=<4000)AND(ABS(SY)=<2400)THEN 2080: REM SMALL MAP 2075 P=.OOl 2080 GOSUB 2803: 2082 GOSUB 4000: 2085 Z=DT*A/D: 2086 REM 2087 REM-------- VELOCITY UPDATE 2088 REM 2090 WX=VX-Z*SX+BX: WY=VY-Z*SY+BY: 2100 SX=SX+DT*.5*(VX+WX):VX=WX: 2110 SY=SY+DT*.5*(VY+WY) :VY=WY: 2120 PX=INT(P*SX) :PY=40*INT(P*SY): 2125 L=33268+PX-PY: 2128 IFL=LlTHEN2000: 2130 POKEL1,46:POKE L,Sl:Ll=L: 2140 GOT02000:

REM MAP DRAW REM ACCEPT BURN REM VEI..OC CHANGE / DIST TO MOON

REM VELOCITIES AT END OF DT REM NEW S/CRAFT POS, VEL

REM POSITION ON MAP DISPLAY REM SCREEN LOCATION REM NO MOVE REM DOT IN OLD, BLOB IN NEW REM MORE SIMULATION

Figure 10. Central simulation loop

note. A stands for 'up arrow' or 'exponentiation' symbol.

14

Page 20: The Alien, Numbereater and other Programs for Personal Computers

ine to display the map is entered at line 2803, it checks to see if the required scale P is different from the last scale it applied. We put MAP to zero here to ensure a difference will occur and a map displayed the first time. It is worth noting that 'MAP' is a legitimate variable name like MA, MAMA or MARMADUKE, but BASIC would ignore all but the first two letters, and treat all references to any of the four as being references to the same variable. Long names can improve program legibility, but can can mislead the programmer. He or she must also guard against using a name which contains a sequence of letters which corresponds to a BASIC key word: FRED includes FRE, and we can even have problems if a pro­grammer-defined name is next to a keyword - even if separated by a space since BASIC ignores spaces - and the end of one word and the beginning of the next make up a key word, for example

IF F OR K THEN 6000

(BASIC sees FOR in this), or

FOR I=l TO BALI STEP 2

( BASIC sees LIST

At line 1050 we initialise a little array V which we can use as a table to convert a digit taken from the PET matrix keypad into an x,y direct­ion - this will be used to control the direction of rocket burn. A 5 will be taken to mean the player wants to accelerate up the screen, a 3 diagonally down and to the right

7 8 9 4 6 1 2 3

There are several approaches to this task and in other programs in this book you will see variations - this is probably the most obvious. The array RX(O) to RX(9) will hold the 'x' or 'left/right' interpretation of digits 0 - 9, and RY(O) to RY(9) the 'y' or 'up/down' interpretation. The BASIC READ statement looks for DATA statements in the text and takes the values in them into the variable following the READ, so the first two values, -1,-1, go into RX(O) and RY(O) as we would require. In setting up the DATA statements I was a little more conscientious than usual to align the values so that they could be checked. It wastes a little space but space is not a problem on this program. (If you read the section on the program GIP, you will find that I have had to make the program as compact as possible, with unfortunate results on legibility).

At 1090 I initialise Ll, another variable used later, to show the last spacecraft position, and E, the energy or fuel remaining. One unit of fuel is enough to accelerate the spacecraft by 1 mile per second. In line 1900 control RETURNS back to the main logic, which next enters the main subroutine at 2000.

Before leaving the topic of the INITialisation subroutine, I should say that it was not written all at once, before the main part was started. On the contrary, I kept on inserting statements as the need for them be­came apparent. My conception of a program is rather like a position in chess or a painting: my attention is mainly concentrated on one area at a time and I prefer it if I can keep it independent from the rest, but occasionally it is necessary to switch attention to another area which is in some way better placed to perform the function I am trying to ach­ieve. Part of the art of programming lies in defining these 'areas' which will end up as subroutines, to have a useful, simple function that one can remember as one writes code elsewhere - and say to oneself, 'Ah,

15

Page 21: The Alien, Numbereater and other Programs for Personal Computers

wait a minute, instead of writing all this out, I could use that sub­routine for drawing lines ( for example ) if in my present section of code I redefined the task a little to be in line with what the subrout­ine expects.'

Sometimes one may find the subroutine is not quite suitable, and after a a few more occasions when it looked promising and then turned out not quite right, the decision has to be taken to redefine its function to improve its general utility. This means going back to the sections of program which used it originally and amending them to fit in with the new function. In the case of INIT, its virtue lies simply in the fact it is executed early, and once only. Since this property is required of some statements in almost all programs, I plan a subroutine 'INIT' semi­automatically when designing any program. Take another situation.

In a program, we need to find the square root of X squared + Y squared. We write a subroutine to do this and GOSUB to it after setting X andY, getting the answer in, say, z. But later on we find the need to find the nth root of (X to the nth+ Y to the nth). So we rewrite the original subroutine to be more general. This means that the original GOSUBs have to be adjusted - in this case to set up not only X and Y but the power to be used, by, say, N=3. A further factor to be borne in mind is that any subroutine, besides having a clear function stated in REMs at its beginning ( please do not point out I do not always remember this rule myself ! ) should fit into one screenful of VDU display, which demands on the PET not more than 25 lines { though each can hold several statements, of course). But I digress: let us continue with ORBIT.

Now to start the main simulation. See Figure 10. REMs describing this subroutine are positioned at 2000-3, 2000-2, and 2000-1 so that the first statement to be executed does some real work - if the REMs began at 2000 it would slow the PET up traversing and ignoring the REMs, which are for human consumption only.

At 2000, then, we calculate the square of the distance of the spacecraft from the moon by Pythagoras. You will recall that the coordinates of the moon are 0,0 so there is no need to write in full:

D2={SX-MX)*{SX-MX) •••••

Note also that although we could tell the PET to square SX by

D2=SXA2 ••••• the upside-down V is an 'up arrow' )

it is faster to write it as shown since the PET exponentiation routine is set up to deal with fractional exponents and, because it is so gen­eralised, takes longer to run. From now on we shall be anxious to save running time, since these statements will be executed very frequently.

Note that 02 is a mnemonic to remind ourselves this is the square of the moon-spacecraft distance. The next statement on the same line takes the square root. ( If there was any chance that a number to be rooted was negative it would have been good practice to test for this condition be­fore using the PET function SQR: but- I hope!- this is impossible).

At line 2010 we calculate the acceleration on the spacecraft due to the moon's gravity, attentuated by the square of its distance away, making use of the constant KM defined earlier. P is the map scale assumed -1/200. This means that one square on the screen will correspond to 200 miles in space: the local map of the small area round the moon is meant.

16

Page 22: The Alien, Numbereater and other Programs for Personal Computers

At line 2020 we begin testing which map is really needed, since setting P to 1/200 is prejudging the matter. First, is the spacecraft even in the area of the big map? The first occasion we execute this test it is certain it will be, since the absolute value of SX will be equal to and not exceed 20000, and similarly for SY. ( ABS is needed because the spacecraft coordinates can go negative depending on its position relat­ive to the moon ) • But subsequently, due to pilot error, the spacecraft may be further away from the moon than when the simulation started: then we must tell the crew the bad news at line 2300 that they have gone too far to come back into range. But normally, and certainly to start with, this test will fail and we test at line 2070 to see if the spacecraft is not only within the big map, but also within the local map area, a rect­angle of 4000 x 2400 miles. The layout of the map rectangles is noted within the subroutine for showing them at lines 2790 following ( if any program is not squeezed for space it is nice to include diagrams of this kind: separate bits of paper get lost). If the coordinates are large, at 2075 we set the scale to 1/1000 appropriately for the big map.

An interesting point on programming style comes up here. In BASIC, IF statements specify what can be done if the condition tested is true, by the THEN clause. But they do not allow us to write an ELSE clause to be taken if and only if the condition is false. We cannot write

IF naughty THEN punish ELSE reward

but get driven into some such sequence as

100 IF naughty THEN 130 110 reward 120 GOTO 140 130 punish 140 continue ••••

This is clumsy, so the BASIC programmer often writes in effect

100 treatment=reward 110 IF naughty THEN treatment=punish 120 apply treatment

This is not really satisfactory either, because the program does some­thing ( the 'treatment=reward' step ) unnecessarily half the time. Also it is bad logic - we assert or assume something, only to repudiate it later. Let us pressurise microcomputer suppliers to give us IF THEN ELSE constructs in BASIC, or a language in which it is standard, such as PASCAL.

When clarity is more important than speed, I sometimes use the following construct

100 IF naughty THEN punish 110 IF not-naughty THEN reward

This is blindingly clear, but at the expense of a redundant test.

At 2080 the subroutine to draw the map is used: we will consider this later. At 2082 ( the statement number implies this was inserted at a later stage in the program ) another subroutine is entered - to get from the keypad the player's decision to fire his rockets, if required. At 2085, z is precalculated - it is the velocity change due to moon gravity divided by the spacecraft - moon distance. We had A already from state­ment 2010. DT is the time increment to be used for each cycle of the simulation. Where has this sprung from? It is provided by the map sub­routine, on the grounds that this routine deals with the different sit-

17

Page 23: The Alien, Numbereater and other Programs for Personal Computers

uations which arise as a consequence of the spacecraft being near or far from the moon - the most obvious one being the scale of map to use, but another is how long a time should elapse in the spacecraft's real world between successive recalculations of its velocity and position. It is because I considered the DRAW subroutine in this more general light that I felt it should deal with the choice of DT also. ( Maybe it would have been more logical therefore to have retitled it to 'ENVIRONMENT-SETTER' or something similar). The implications of the choice of DT are dis­cussed later.

At 2086-2088 I put in some REMs to make it easy to identify this bit of program when LISTing it, and the statements scroll up the screen fast. I have to accept that this luxury will slow up the program a bit. At 2090 the new velocity components at the end of the current time increment DT, WX and WY, along the x and y axes respectively, are computed from the components VX and VY at the end of the last DT, plus the change brought about by the moon's pull, z, and by any velocity change induced by fir­ing a thruster along one of the possible axes, giving A, which we take as the acceleration towards the moon along the line from moon to space­craft. To resolve this along the two axes x and y, it is necessary to multiply A by the sine and cosine respectively of the angle of this line to the x axis. However, to use trigonometrical functions is rather time consuming, and unnecessary, since we can calculate the sine and cosine by dividing SX and SY by the distance D: in fact we precalculated as much of this as we could when we worked out z at line 2085.

BX and BY are the increments in WX and WY due to rocket burn: they will usually be 0, but occasionally they may take values 1 or -1 according to the digit pressed by the player.

At line 2100 a further integration step is carried out: the average speed over the last DT is worked out as ( VX + WX )/2 and multiplied by DT to give the extra distance travelled, to give a new sx, and similarly for SY. In each case, the old velocity VX,VY is replaced by the new one ready for the next cycle. Now it has to be pointed out that this method of integration by small increments of DT has a drawback. If DT is too large, the velocity increase as the spacecraft approaches the moon will be overstated, since the acceleration used to compute WX and WY is that obtaining at the end of the DT, i.e. when the spacecraft is nearest to the moon. Hence it would have been more correct to have kept the accel­eration at the start of the DT and averaged it with the A at the end, in exactly the same way as was done with the velocities. It is left to you to modify the program - but first see what the effect of the present shortcoming is.

So let us break off from the discussion about the content of the program and see it in action. Key in RUN. The screen shows the moon as an 0 in the middle, a few random stars, and the spacecraft at bottom left. After a pause it moves, leaving a dot behind it. Slowly it gathers speed, and this can be seen in the MILES/SEC display. Each cycle of the program co­rresponds to 1800 seconds of ship time - otherwise it would take a day or two to run the simulation. See Figure 11.

If nothing is done we will drop into the moon at over a mile a second, so it is advisable to key in a 3 or a 7 to cause a small change of vel­city at right angles to the path and so make the spaceship pass to one side or other of the moon. As we approach at high speed, suddenly the display is replaced by the local map with the moon scaled up to size and DT now reduced to only 30 sees, so events happen more slowly and the integration will be carried out less coarsely. See Figure 12.

The task is to get the spacecraft into a near-circular orbit which does not intersect the surface of the moon! At present it is in a very large elliptical orbit. So as it approaches the moon, just at the required distance away for the correct orbit, fire a rocket to decelerate it.

18

Page 24: The Alien, Numbereater and other Programs for Personal Computers

Figure 11. Approaching the moon

8 9

6

3

Figure 12. Skimming the craters

0 F F 0 F F 0 F F 0 F F 0 F F 0 F F 0 F F 0 F F 0 F F

+

Figure 13. Too many thruster corrections

Page 25: The Alien, Numbereater and other Programs for Personal Computers

This is done by pressing one of the digit keys 1 to 9. The routine for interpreting this as an order to fire a rocket will be discussed later when we consider the ROCKET BURN subroutine in Figure 18. However, the effect is to load BX and BY with values indicating acceleration in the X and Y axes. If you press one of these keys so many times that you run out of energy, the display in Figure 13 will appear. In fact, the panicky look of this display - the message repeated again and again -was achieved rather by accident than design, but looked quite pleasing and so was left.

To get into a reasonably stable orbit the trick is to pass to one side of the moon and, just as the distance stops decreasing and starts to increase again, to fire a rocket approximately 180 degrees to the dir­ection of motion, so as to reduce velocity while maintaining the tang­ential direction. A good idea is to work out a program on a pocket cal­culator to give the required orbital velocity for a given distance from the moon's centre, and to use this to compute how many retro-burns will be needed to reduce speed to this velocity. It is not a good idea to thrust along the spacecraft-to-moon line, even though intuitively this looks the right thing to do to get nearer or further from it.

After you have experimented with the program a little to investigate the shortcomings of the simulation, and have accidentally penetrated to the centre of the moon (asH G Wells' First Men in the Moon did), and have been slung out of the neighbourhood of the moon to perish miserably in the far reaches of space, or possibly burned up the last of your fuel and even lost the display, you may feel like returning to consider the remaining parts of the program we have not yet looked at. Figure 14 is fairly self-explanatory - the only interesting points are the odd '3' in line 2300, which is my IBM printer's attempt at a PET 'reverse heart' symbol, to clear the screen. In 2340 the program, instead of executing a STOP, loops to itself so that the display is not spoiled by a READY.

Now consider the MAP DRAW procedure (Figure 15.) described earlier in outline. Because this section contains many PET graphics symbols I have photographed it as it appears on the PET screen, as Figure 16, without some of the REMs shown in the printout version of Figure 15. ( The narrow 40 character PET display makes it confusing to put REMs on the far right of the line). The boundaries of the map area are displayed in lines 2793- 2802: the large scale map covers 40 000 miles x 25 000 miles and the small map, 8 000 by 5 000. The areas are not rectangular because the PET screen is 40 characters wide by 25 deep ( at least, my 1979 model is! ). In passing I should remark that the characters them­selves are not square either, which means that a circular orbit will ap­pear elliptical on the display.

At 2803 a test is made to see if the scale P has changed as a result of the spacecraft moving through the boundary between the two maps, and if not, all that has to be done is a re-write of speed, fuel and distance. But if there is a change, we begin at line 2804 by setting MAP to P so as to prevent another draw on the next entry to the subroutine, and we set the DT 'delta-time' for the simulation to 30 x 60 seconds - so the program will be updating velocity etc every 30 minutes. This may be countermanded shortly, however: for after clearing the screen with the 'reverse heart' and scattering some asterisks round the screen to re­present stars, in line 2805, we look more closely at MAP ( reflecting P now ) and if it is .001 i.e. set for the large map, move the screen cursor down using the 'reverse Q', that is, cursor-down, symbol, and across, using the TAB, to the middle of the screen, where we manufacture a little moon out of four PET symbols each of which is one quarter-arc of a circle, using another cursor-down and two cursor-back symbols to position the cursor to write the two lower arcs.

20

Page 26: The Alien, Numbereater and other Programs for Personal Computers

2297 REM 2298 REM------LOST IN SPACE 2299 REM 2300 PRINT"3YOU ARE OUT OF RANGE. 2310 PRINT"LITTLE CHANCE OF SURVIVAL. 2320 PRINT"DON'T TALK OR MOVE MUCH. 2330 PRINT"TO END IT ALL, PRESS STOP. 2340 GOTO 2340: REM TO AVOID 'READY.'

READY. Figure 14. Moon flyby

2790 REM 2791 REM-------MAP DRAW

REM" REM"! REM"! REM"!

2792 REM 2793 2794 2795 2796 2797 2798 2799

REM"25 REM" X REM"lOOO

2800 REM"! 2801 REM"! 2802 REM"!

40 X

40 X

25 0 X

200

1000

200 ----!

2803 IF P-MA~~P~T~H~E~N~2~8~7~0-:------------ REM NO CHANGE 2804 Ll=l:MAP=P:DT=30*60: REM ASSUME BIG ~1AP 2805 PRINT"3":FOR I=l TO 7:POKE 32767+1000*RND(I),ASC("*") :NEXT: REM STARS 2810 IF MAP=. 001 THEN PRINT'"' ;TAB (19); "ui==jk": REM TINY MOON 2815 IF MAP=.OOl THEN 2850 2820 DT=30:PRINT"": REM BIG MOON 2830 PRINTTAB(l7); "----" 2832 PRINTTAB(l6); "------" 2834 PRINTTAB(l5); "--------" 2836 PRINTTAB(l5); "--------" 2838 PRINTTAB (16); "------" 2840 PRINTTAB (17); "----" 2850 PRINT"";TAB(l5);"(SECS PER DISPLAY";DT;")" 2865 PRINT"7 8 9" 2866 PRINT"4 6" 2867 PRINT"! 2 3111"; 2870 PRINTTAB(l6);"M/SEC ";LEFT$(STR$(SQR(VXA2+VYII2)},4},"FUEL=";El 2880 PRINTTAB(l6};"DIST ";RIGHT$(" "+STR$(0),6} ;"11" 2900 RETURN

READY. Figure 15. Graphics: two map scales and moon sizes

note. To show the PET special graphics characters, refer to the photograph of this part of the program shown as Figure 16. On the IBM printer, these print differently: for example the 'reverse heart' clear-screen character appears as a '3'.

21

Page 27: The Alien, Numbereater and other Programs for Personal Computers

In 2815 you may spot a difference between the photographed program and the printed version - 1/1000 rather than .001. All I can say is that I follow a 'policy of continuous improvement' and 1/1000 takes longer to evaluate than .001. At all events, if MAP was for the large map, the set-up is complete and we can go on to write out the velocity etc. But otherwise we set up the small map in lines 2820 following, starting with a delta-time of 30 seconds, so that more accurate integration is done nearer the moon when accelerations and speeds are changing rapidly, and when the crew have the impression that the display is changing more slowly, giving them more time to react. A nice big moon is drawn in lines 2830-2840. In 2850 reverse-S moves the cursor to the top left of the screen. DT and any rocket-burns are shown. In 2870 a 'reverse-R' turns all the remaining characters on this line to black-on-white mode instead of the normal white-on-black. We can actually include a calcul­ation in the middle of our printing - to work out the velocity from its two X and Y components by Pythagoras - and this number is converted by STR$ into character format ready for printing. But since we want to avoid all the places of decimals, we use LEFT$ to take just the four most significant ones, i.e. a sign (always a space), and two signific­ant digits and a decimal pqint, giving ( for example ) 1.2 or .99. There is no need to go to these lengths to show E since it is already an integer ( and could have been renamed E% to indicate the fact to BASIC, giving it the opportunity to save a bit of calculation time).

\1 !s :: ~ o ~::•] II X X 11 1888 288 II 'L----------' II

PRINTTI1Bt17•. "----" PRINTTAB<16>. "------" PR INTTAB< 15>. "--------" PR HHTAB < 15); "--------"

2838 PR IN TTAB < 16 > , "------" 2840 PRINTTAB<17>; "----·~ 2850 PRINT 111!!1 11 ;TAB<15>.~~~sE.._S

Y"·DT· 11 ) 11

2965' PR INT 117 8 II

PEP

2866 PR INT 114 2867 PR INT 11 1 2 2870 PR INTTAB < 16

( SQR <VXt2+VYt2> > .L 4j ~ 2880 PR INTTAB ( 1b)' I

II +STR$ <D> ,6) 'llrnll 2900 RETURN

M/SEC II. LEF Tf' ·=· Tl= FUEL=".£:1 1ST II .PI•jHTf·"

Figure 16. Part of ORBIT showing PET graphics

Page 28: The Alien, Numbereater and other Programs for Personal Computers

At 2880 D for distance is printed. Normally this is left-justified, and that would spoil the layout. To force right-justification, we add a good chunk of spaces onto the front of the STR$ ( i.e. character form ) of D, and use the right-hand six characters of the result. A couple of 'reverse blobs' i.e. cursor-ups follow, to get the cursor onto the right line for the next time this bit of program is entered. That is the end of the MAP DRAW routine, which, as was noted earlier, does rather more than setting up the map - it displays the instrument values and sets DT as well, so I am somewhat unhappy about it: perhaps I should have split it up a little, since, although it seems to work well, the time will come when I want to modify it and its multiple nature will confuse me.

Another source of shame is FINISH (Figure 17 ). This routine was in­cluded because it is always good practice to design a program ~o have sections marked INIT, MAIN, FINISH, but in this case, MAIN ( here called 'GAME ' ) seemed to want to go on indefinitely. Perhaps we would have needed FINISH if statistics had been kept, so that the experimenter could press· a key to stop the simulation, enter FINISH, and receiv~ a debrief on his performance - 'you crashed only 4 times, an improvement of 25%' etc. So this FINISH is the opposite of the human vestigial app­endix - an organ whose time has not yet come, rather than one whose time has passed.

Lastly, consider procedure ROCKET BURN (Figure 18 ). 4000 forces the burn values to zero and then tests the keyboard buffer to see if the ex­perimenter has pressed a key. If not, A$ will be null and we can bypass the decomposition of the key-depression in lines 4010 following. In fact, I do not see why the program could not go straight to 4060 RETURN here - except that by so doing we should lose the rather nice panicky OUT OF FUEL business. But suppose a key has been pressed. VAL converts it into a digit - zero, if any but 1 - 9 were pressed. So if V = 0 we ignore the key depression as a mistake ( we could have organised a sarc­astic message if we had wished). At 4020 the digit is used to get X and Y values +1, 0, or -1 for the acceleration in these axes. Note that if 5 was pressed, 0,0 will be used. 4040 locates the spot on the screen where the digit is displayed which has just been pressed, and this digit is loaded into R, and five times put back with 128 added to make it appear in reverse field, then back again. In this way the experimenter will see the rocket burn registered on his instruments.

At 4050 the burn impulse is scaled rather roughly { as a result of early trials when it was found that the rocket burns were too savage - it would of course have been better to have revised the DATA statements in INIT, but this would have been a bore. In 4055 the total energy left is reduced by the absolute value of the burns in each axis - ABS removes"the sign if any, and is needed because a rocket uses energy whether its effect is to increase or decrease speed, unfortunately. At 4057 we make sure there is still fuel left - if not, the spacecraft is done for.

This concludes the section on ORBIT, which discussed some of the topics that arise in simulation and graphics. It is hoped that readers can de­velop their own simulations with, perhaps, more accurate mathematical models particularly as regards integration. It would be fun to use the program as the basis for a complete launch-to-moon-to-ocean-recovery mission in which the US and USSR competed: there are plenty of lunar landers to do the descent-to-the-surface part.

By the way, a dot left over from the space-craft trail drawn in 'big map' mode is left spoiling the bottom of the moon when a switch to 'small map' occurs. ( Figure 12. ) Perhaps you can correct this error for me.

23

Page 29: The Alien, Numbereater and other Programs for Personal Computers

2998 REM 2999 REM------- FINISH ( NOT REACHED ) 3000 REM 3997 REM

READY. Figure 17. Because there is no exit from Figure 10

3998 REM-------ROCKET BURN 3999 REM 4000 BX=O:BY=O 4005 GETA$:IFA$= 1111THEN 4050: 4010 V=VAL(A$):IF V=O THEN 4050: 4020 BX=RX(V):BY=RY(V): 4040 Q=32890+BX+BX-80*BY: 4045 R=PEEK(Q): FOR J=l TO 5 4046 POKE Q,R+l28:FORI=lT0200:NEXT: 4047 POKE Q,R:FOR I=lT0200:NEXT I,J 4050 BX=BX*.l:BY=BY*.l 4055 E=E-5*(ABS(BX)+ABS(BY)):El=INT(E) 4057 IF E>O THEN 4060: 4 0 58 PRINT II 0 u T 0 F F u E L ! ! II 4059 BX=O:BY=O 4060 RETURN

READY. Figure 18. Rocket Burn

1 REM -----PASCAL----------------------2 REM 5 PRINT 11P A S C A L 1 S T R I A N G L E 11

10 FOR T=O TO 9 15 PRINT TAB(2*(9-T)); 20 V=l 30 FOR I=l TO T+l

REM NO BURN REM NOT 1 TO 9 SO IGNORE REM BURN VECTOR FROM TABLE REM SPOT ON ROCKET-STATUS

REM FLASH THIS SPOT FOR A MO

REM STILL SOME FUEL LEFT

40 V$ = II "+STR$ (V): V$=RIGHT$ (V$,4): PRINT V$; 50 V = V*(T-I+l)/I 70 NEXT I 80 PRINT: PRINT 90 NEXT T

READY. Figure 19. PASCAL program

P A S C A L 1 S T R I A N G L E 1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

1 5 10 10 5 1

1 6 15 20 15 6

1 7 21 35 35 21 7

1 8 28 56 70 56 28

1 9 36 84 126 126 84 36

1

1

8 1

9 1

Figure 20. Results of PASCAL program

Page 30: The Alien, Numbereater and other Programs for Personal Computers

Pascal's Triangle Techniques: TAB (tabulate): conversion of numeric values to strings for output.

Pascal's triangle is composed of rows of integers, each row containing one more integer than the row above. The digit 1 forms the apex. On lower rows, each integer is calculated as the sum of the two integers above it. See Figure 19 and 20 for program and results.

The integers correspond to the coefficients found in the binomial ex­pansion

T (1+1)

T T(T-1) T! 1 +- + ------ + •• + --------

1 1.2 I! (T-I)!

where I is the term number starting with I = 0 at the left, and T is the power to which 2 is raised, and also the row number with T = 0 the apex. Note that each term can be found by multiplying its predecessor by:

(T-I+l)/I

In this program we write ten rows, with T increasing from 0 to 9 ( see line 10 ). In 15, we work out where to start printing the first integer. Each line is going to start half an integer space to the left of the one above. Each integer needs four character positions - a space and three digits. So we have to begin each row with a 'TAB' of as many spaces as the preceding row less half the allowance of four characters: hence the expression

(2* (9-T))

in line 15. On the first row, which corresponds to T be 18.

0, the TAB will

In 20 we set the first value to be printed as 1, then start printing the rows. At 40 we compose a string V$ as two spaces followed by the alpha­betic representation of V. Then we take just the right-hand four char­acters and print them, preventing a new line by following the PRINT with a semicolon.

At 50 we compute the next integer in the current row by the recurrence relation shown, and continue: but when we have finished the row ( when I reaches one more than the row number T ), we force a couple of line feeds, and go on to the next row.

It would be an interesting exercise to calculate the numbers by the method mentioned first - i.e. examining the line/row above and picking up the two numbers whose sum the new number should be. Before doing this it is instructive to prove that

T! (T - 1)! (T - 1)! ------------------ + ----------

(T-I) !I! (T-1- (I-1))! (I-1)! (T-1-I)! I!

i.e. that any term can be calculated as the sum of the two terms on the preceding row T-1, with term numbers I and (I-1).

It is also advisable to see how one can extract numeric values from the screen - see the section describing GIP the General Input Program, which contains a routine for doing this as part of a 'clear program' step.

25

Page 31: The Alien, Numbereater and other Programs for Personal Computers

Techniques: machine code programming, list processing, integers.

Superlife got its boastful title because it was so much better than my early attempts, which ran very slowly. It is an implementation of the 'game of life' invented by the famous Cambridge mathematician Conway.

'Life' is a simulation of the growth and decay of populations of cells, each of which survives or dies according to three simple rules: if it has less than two neighbours it dies (of 'loneliness' ): if it has two or three neighbours it survives, but if it has more than three it dies 'of overcrowding'. In addition, a cell spontaneously comes to life in an unoccupied space, if the space has exactly three neighbouring spaces occupied by cells: a trisexual society?

Hence one can predict that the following configurations of cells

* *** ** *

*** *** ***

will become, in the next generation,

* * *

** **

* ***

** ** *** *

It is interesting to take a sheet of squared paper, enter some arbitrary patterns, and trace their survival, growth, death, coalescence, fission and motion, which are brought about by the repeated operation of these simple rules, generation after generation. It is also very laborious so a computer simulation is a much easier way to proceed.

Just what philosophical conclusions one reaches from observing 'life' is very much a function of the individual, but analogies can be drawn between it and bacteria in a Petrie dish, or cosmic proto-galaxies, or even trade unions! It is possible to discover initial configurations which 'glide' across the field, such as

*** * * *

There are also 'glider guns', 'harvesters' and other interesting pat­terns.

First, try running the program. Press any digit to get started, then construct your configuration, say about twenty or thirty cells, by using the digit matrix as a cursor controller in the same way as is done in Numbereater. A cell is established by pressing the centre digit, 5. See Figure 21 for a typical configuration being established in this way.

When you have finished, press 0, and the program will begin at verse 1, chapter 1 of this particular mini-universe's Genesis. You will feel quite God-like! Notice the line of asterisks across the top of the VDU: they are not just decorative, but serve to prevent the cells from grow­ing out of the screen area of memory and propagating into parts of mem­ory where they will be unseen, but quite possibly injuring the program

26

Superlife

Page 32: The Alien, Numbereater and other Programs for Personal Computers

itself - as if a dangerous virus which we are studying must be held within a safety area to avoid a health hazard.

You will see that in preparation for the check for new life, which can arise only in spaces which are neighbours of existing live cells, the program draws a nice border round the pattern on each generation, then removes it, leaving, possibly, some newly created cells within its area.

The program goes on until it detects no births or deaths, when it gives us the message

ALL OVER, FOLKS

If the population is just oscillating, the program will keep on running.

••• II I I • I • • II II II • Ill II I II • • I I • • Ill II • • II I II I

I II II •• I • II Ill •• ••

Figure 21. LIFE program running

Now let us study the program (Figure 22). At lines 5- 20 we define arrays of integers - recognised by the % after the vari able name, since BASIC works faster with integers than real numbers, and in this program we are concerned about running speed - in fact this is why the central part - the counting of neighbours - is written in machine language, to run faster by a factor of ten or so. In A%() we propose to store values which correspond to the positions on the PET screen where a cell exists, except that the top leftmost position is numbered 1 rather than its real memory location 32768.

C%() and D%() are tricky. They are arrays of values which can be used as indices into the arrays A%() and B%(). Specifically, if C%(2) or any other C%() was equal to 5, this would mean that a cell is due to be Cre­ated at A%(5), and if aD%() held a value, it would mean the Death or Deletion of the corresponding cell in A%().

27

Page 33: The Alien, Numbereater and other Programs for Personal Computers

1 REM------- SUPERLIFE -----------2 REM 10 DIM A%(255): 12 DIM B%(255): 20 DIM C%(255): 22 DIM D%(255): 25 REM 30 GOSUB 100: REM SET UP 35 GOSUB 700: REM BUILD LISTS 40 GOSUB 300: REM CHANGES SO GOSUB 400: REM APPLY CHANGES 60 GOTO 40 65 REM 66 REM--------SET UP 67 REM 100 GOSUB 1530:GOSUB200 110 GOSUB 250: 120 IF A$ <> "O"THEN 110: 130 RETURN

REM SCREEN LOCS -32767 OF CELLS REM LOCS OF NEIGHBOURS REM POINTERS TO B%() TO CREATE REM POINTERS TO A%() TO DELETE

REM YOU BUILD SHAPE ON VDU REM UNTIL YOU PRESS 0

Figure 22. Main logic of SUPERLIFE and SET UP

150 REM 155 REM-------INITIALISE 156 REM 200 NA%=0: NB%=0: G=O: POKE 59468,12: 205 Z$="*************" 210 PRINT"S TO RECORD CELL,O TO END, 220 PRINT"OTHER DIGITS TO MOVE 225 REM 226 REM-------LET MESSAGE SINK IN 227 REM 230 GETA$: IF A$="" THEN 230 240 PRINT"3": X%=21: Y%=12: RETURN 244 REM 245 REM-------MOVE CURSOR, ENTER BLOB 246 REM 250 L%=X%+40*(25-Y%): Z=l02: GOSUBSOO: 260 GETA$: IF A$="" THEN 260 265 IF A$ ="0" THEN 299: 268 IF A$<>"5" THEN 285: 270 GOSUB 600: 275 GOTO 260 285 Z=32: GOSUB 500: 287 I=VAL(A$): J=INT((I+2)/3) - 2: 290 Y%=Y%+J: J=I 295 J=J-3: IF J>O THEN 295 296 X%=X%+J+l: L%=X%+40*(25-Y%): 298 Z=l02: GOSUB 500 : 299 RETURN

REM GRAPHICS MODE

REM PLOT BLOB ON VDU

REM YOU'VE FINISHED REM STILL MOVING CURSOR REM ENTER CELL

REM PLOT A BLANK ON VDU REM DECODE KEY JUST PRESSED

REM CALC CURSOR'S NEW POSITION REM PLOT A BLOB

Figure 23. INITIALISE and build initial shape

28

Page 34: The Alien, Numbereater and other Programs for Personal Computers

In 30 - 60 we spell out the overall structure of the program. It looks as if it will go on for ever, but in fact in 'APPLY CHANGES' a STOP is reached if there are none. Note - it would have been better practice to have brought this out into the main program logic here.

The SETUP procedure starts at line 100. 1530 loads the machine code, we will examine this later. 200 clears some values and gives instructions. 250 organises the creation of the starting pattern, which goes on until the digit pressed by the experimenter is a 0. Look at these in detail.

In INITIALISE ( Figure 23 ) , we first clear NA% and NB%, which count the number of entries in A%() and B%(), and G, the number of generat­ions. Z$ is a nice string of asterisks for building a cage round our cells. Notice how we avoid a common error in programs - instructions are presented, but no time is given to read them and they flash past in an instant. At line 230 the GET monitors the keyboard and A$ stays null ( represented by the pair of quotes ) until any key, apart from RUN/STOP is depressed. ( Note also that because I am using a home-made word­processing system, I cannot type a double quotation mark in the sentence above: see the section on GIP ).

Line 240 clears the screen ('3' is really a reverse-heart ) and sets X,Y for the home-made cursor to appear roughly in the middle of the screen. This marks the end of the initialisation subroutine and we go back to line 110, where we start building the initial configuration by using the subroutine at 250.

This first computes the address in the memory area mapped onto the screen, corresponding to the x,y coordinates of the cursor. Note that (25-Y) turns our convention of Y going upwards from the bottom of the screen, into one in which Y increases as you go downwards, so as to conform with the PET's arrangement, in which screen addresses go up by 40 for every line you go down. z is the ASCII character code for a PET symbol consisting of a grey square, representing our cursor.

We use subroutine 500 ( see Figure 26 for details ) to show the cursor on the VDU, unless the position indicated already holds a character with ASCII code 81 - a cell. This means that once a cell is created during this set-up phase, it cannot be got rid of. This would be bad practice in anything but a game - one should always be able to correct errors caused by the slip of a finger. It is not so important here: if the experimenter does not mind starting again without too much bad language.

At line 260 we test the keyboard and when a stroke is caught, proceed to 265 and test for a 0, which marks the end of the building of the in­itial pattern. Otherwise we see if it is a 5, which means the exper­imenter wants to record a cell. Subroutine 600 does this for us - it uses 500 to enter the blob shape unless there is one there already, and also enters the cell's address in the array A%().

However, if the digit pressed is not a 0 nor a 5, we know the cursor is about to move. Subroutine 500 is used to put a space (32) into the place where the cursor is now, and which it will shortly vacate.

Now we must work out where the cursor is to go: we convert the character value of the digit into a numeric value at line 287, using the function VAL, and manipulate it into a value J with a range of ( -1, 0, +1 ) as I takes values (1,2,3), (4,5,6), (7,8,9): in other words if the top row of digit keys was pressed, J will become +1, i.e. we want the cursor to move up the screen. Similarly it is to move down if a key in the bottom row is pressed, and to stay at the same Y if a key in the middle

29

Page 35: The Alien, Numbereater and other Programs for Personal Computers

300 REM 302 REM-------FIND CHANGES 303 REM 305 G=G+l: PRINT"lDOING GENERATION" ,G;Z$ 310 NC%=0: ND%=0: 320 FOR I=l TO NA%: L%=A%(I) 3 30 GOSUB 900: 340 IF K%=2 OR K%=3THEN 360: 350 ND%=ND%+1: D%(ND%)=I: 360 NEXT I 370 FOR I=l TO NB%: L%=B%(I) 375 GOSUB 900: POKE 32767+L%,32: 380 IF K%<>3 THEN 390 385 NC%=NC%+1: C%(NC%)=I: 390 NEXT I 399 RETURN 400 REM--------APPLY CHANGES 405 REM 410 IF NC%=0 THEN 475: 415 FOR I=l TO NC%: J=C%(I) 420 L%=B%(J): POKE 32767+L%,81: 425 NA%=NA%+1: A%(NA%)=L% 470 NEXT I

Figure 24. Changes

471 REM 472 REM-------DELETIONS 473 REM 475 IF ND%=0 THEN 496: 477 FOR I=l TO ND%: J=D%(I) 480 L%=A%(J): POKE 32767+L%,32: 486 A%(J)=O: NEXT I: J=l: 487 FOR I=l TO NA%: IF A%(I)=O THEN 495 490 A%(J)=A%(I): J=J+l 495 NEXT I: NA%=NA%-ND% 496 IF ND%+NC%>0 AND NA%>0 THEN 498 497 PRINT"ALL OVER FOLKS ";Z$: STOP 498 GOSUB 735: 499 RETURN

Figure 25. Deletions

500 REM 501 REM-------PLOT BLOB IF NOT ALREADY 502 REM 505 IF PEEK(32767+L%)=81 THEN 520 510 POKE 32767+L%,Z 520 RETURN 600 IF PEEK(32767+L%)=81 THEN 610 605 Z=81: GOSUB500: NA%=NA%+1 607 A%(NA%)=L% 610 RETURN

Figure 26. Display 'Blob'

30

REM RESET CREATE /KILL COUNTS

REM COUNT NEIGHBOURS REM SURVIVE OR CREATE REM KILL!

REM COUNT N 1 BOURS & BLANK

REM LET THERE BE LIFE!

REM NO CREATIONS NEEDED

REM PLOT NEW LIFE, ADD TO LIST

REM NO DELETIONS NEEDED

REM BLANK DEAD CELL REM CONDENSE LIST

REM REDO NEIGHBOURS

Page 36: The Alien, Numbereater and other Programs for Personal Computers

699 REM 700 REM-------BUILD LISTS 701 REM 735 PRINT"NEIGHBOUR LIST"~Z$ 737 NB%=0:IF NA%=0 THEN 799 740 FOR I=l TO NA% 750 FOR J=-40 TO 40 STEP 40 760 FOR K=-1 TO +1 765 IF J+K=O THEN 790 770 L%=A%(I)+J+K 772 N%=PEEK(32767+L%) 773 IF N%=81 OR N%=102 THEN 790 778 NB%=NB%+1: B%(NB%)=L% 780 POKE 32767+L%,102 790 NEXT K: NEXT J: NEXT I

Figure 27. Build Lists

898 REM-------COUNT NEIGHBOURS 899 REM 900 GOTO 940: K%=0: FOR YD=-40 TO 40 STEP 40 910 FOR XD=-1 TO +1 915 IF XD+YD=O THEN 935 920 IFPEEK(32767+L%+YD+XD)<>81 THEN 935 930 K%=K%+1 935 NEXT XD,YD: GOTO 950 940 Q=L%: K%=USR(Q) 950 RETURN

Figure 28. COUNT NEIGHBOURS: BASIC or machine language

31

Page 37: The Alien, Numbereater and other Programs for Personal Computers

1530 load m/c code

600 enter cell

100 Setup

!

200 initialise

250 get character,

Superlife !

move cursor ! ! -----!

!

500 enter character

300 400 Changes Apply

! Changes !

900 count neighbours

!

033A m/c code

! ! 700 build lists

Figure 29. SUPERLIFE program structure.

We shall use 700, 'Build lists', again and again during the running of the program, but via 400 'Apply changes' instead of directly. Now con­sider 300, 'Changes'. This subroutine is to determine which occupied cells are to die on the next generation, and which empty cells ( we need only look at those which are neighbours ) are to become occupied by new­ly created cells.

After updating ( ugly but convenient word ) the generation count we re­set the indices NC% and ND% to zero - they show how many creations and destructions are to be done. Then we look at all the ( NA% ) elements of array A%() which you will recall contains the screen positions of the present generation of cells. On the first occasion this point is reach­ed, they are simply the ones we created ourselves in our God-role. Note that at line 320 the array element A%(!) is transferred to a work vari­able L%. This is to improve running speed, since there are two refer­ences to this variable and it is quicker for the BASIC interpreter to refer to a 'scalar' ( mathematical name for a simple variable ) than to an array element.

The 'count neighbours' operation will be described later. The count K%, returned from the subroutine at 900, is now used in the application of Conway's rules to the population. A new cell is added to the list of cells to be destroyed at line 350 and the count increased. Why, you may ask, do we not destroy it immediately, instead of waiting until later? The reason is that we complete the examination of all cells before be­ginning the destroying/creating process, much as wood-cutters mark all the trees to axe before starting to axe them. We want a doomed cell to have the chance of fathering a posthumous child in the next generation.

Next, at 370 following we scan each element of the array of neighbours

32

Page 38: The Alien, Numbereater and other Programs for Personal Computers

B%(), for members which have exactly 3 cells nearby, so qualifying as cradles for the next generation. Now is a convenient time to stick a space into the old neighbour position ready for the next bout of neigh­bour-creation.

The scene is set for the creation/destruction process performed at 400. If there are zero cells to create , we skip lines 415 following, but if there are, we extract the address of the neighbour about to take a cell, C%(I), put it in J, and use it to index the neighbour array itself, at B%(J). Now at last we have the address on the screen ( 1- 1000) of the place we want to place a blob representing a cell, but first the PET memory location is found by adding the offset 32767 to it. ( This numb­er is not as arbitrary as it looks: it is

15 2 1

and marks the end of the block of random access memory in the PET immed­iately before the screen area 32768 - 33767 ) •

At 475 we action the deletions, if any, otherwise we bypass the relevant lines 477 - 495 in ~·thich the screen address of th~ cell to kill is found as 32767 + L% as before. As a matter of fact, replacing 32767 by, say, VS% ( for VDU Start ) set during initialisation to 32767, would improve speed a fraction, since BASIC has to convert 32767 from decimal to bin­ary every time).

There is something special about deletions. Additions we just add onto the end of the array, but deletions may well be in the middle. If we are not careful, our array will become full of holes, and be unnecessarily long and time-consuming to traverse. Hence we arrange to condense the array by the following procedure. J is the index into the new condensed array. The old array, indexed by I, is scanned, and non-zero elements transferred into the new condensed array at position J: J is increased on each transfer, but stays the same when an element in the old is zero and omitted. As it happens, the new condensed array is built up in just the same A%() as the original. This is no problem since the new A%(J) elements are entered behind, or at any rate not ahead, of the old one.

At 495 the count of cells NA% is revised by subtracting the number of deletions ND%. Looking at this again, it strikes me that NA% = J would be more effective - but maybe a little less clear.

At 496 we test to see if there have been any additions or deletions at all: if not, the program can stop. Note that if the pattern is oscill­ating, as in the case of 'traffic lights'

*** * * *

*** * * *

the program will keep running because there are changes still happening.

You may be slightly puzzled that a test should be necessary at 496 when we have just been actioning some deletions. But then you will recall that if there had been no deletions, the IF statement in line 475 would have taken us here. This is an unpleasant consequence of BASIC - since it uses line numbers as addresses for branches you cannot tell whether a given line is branched to from somewhere else in the program or not. This means that whenever a statement is changed there is a risk of a THEN or GOTO arriving from some unsuspected quarter and finding its destination altered or even non-existent. To guard against this one may have to scan the whole program looking for references to this line num-

33

Page 39: The Alien, Numbereater and other Programs for Personal Computers

ber, unless you have a special bit of software to display all cross­references. In other languages, statements reached by other paths than from the statement above are 'labelled' very explicitly, e.g.:

I

IF A> 3 GOTO CALC

CALC: D = 2 * R * 3.14159265 I

(label)

so at any rate one knows that this statement may be reached from another part of the program - then a search is necessary as before.

The answer to this situation, which is dangerous in all languages and very dangerous in BASIC, is to avoid GOTO and THEN <statement number> as far as possible, for example by the CASE statement, if the language supports it, and always to avoid transfers of control over long dist­ances - outside the amount of text displayed on your VDU screen, or, what should amount to the same thing, outside the boundaries of a module of program.

Perhaps I should have written, round about line 410,

IF NC%+ND% = 0 THEN PRINT'THATS ALL, FOLKS':RETURN

which vmuld be pretty obvio';ls, even if I then had to test NC% and ND% individually. I should still be unhappy about the RETURN: it is bad practice to have more than one exit from a subroutine since if one finds later something has to be added to the subroutine one may overlook the 'back door' exit. Perhaps the best of a bad job is to write

IF NC%+ND% = 0 THEN PRINT 1 THATS ALL, FOLKS 1 : GOTO 4 99: REl-1 RET

One nore aside, that message was a feature of a project done by a stud­ent of mine some years ago. I was quite shocked at the informality of it - now I like it!

Before we leave this subroutine, note that the BUILD LISTS procedure is used once again to set up the neighbours. Actually, it is misplaced here, although it works perfectly well, and should really be invoked by the simple amendment of altering line 60 to

GOTO 35

tihat is left to cover? The only procedure not described is COUNT NEIGH­BOURS, at 900, and the loading system at 1530 - Figures 28, 31.

When this version of Conway's Life was first 'lllritten it ran horribly slowly. Analysis showed that the counting of neighbours wan a simple and frequently performed task, and was therefore a good candidate for being recoded in machine language instead of BASIC. At the time I did not have an Assembler, and so tile program was written entirely by hand. At line 900 you will see an unconditional GOTO which branches past some BASIC which carries out the same task - this is the original, slow meth­od. It is instructive to re.'l[love the GOTO and thus reactivate the BASIC version, and to time the two approaches.

The BASIC procedure is much the same as that described under 700 BUILD LISTS: the count is accumulated in K%. But let us stud7 line 940.

34

Page 40: The Alien, Numbereater and other Programs for Personal Computers

1000 REM 6502 COUNT-NEIGHBOURS 1010 REM 826 INT=L% 20 A7 DO 1020 REM 829 A=LSB AS B4 1030 REM 831 CLC 18 1040 REM 832 CLD DB 1050 REM 833 A=A-41 69 D6 1060 REM 835 M=A 85 B4 1070 REM 837 A=MSB AS B3 1080 REM 839 A=A-0 69 7F 1090 REM 841 M=A 85 BS 1100 REM 843 X=O A2 00 1110 REM 845 Y=O AO 00 ll20 REM 847 A=INX B1 B4 1130 REM 849 A:81 C9 51 1140 REM 851 <> DO 01 1150 REM 853 X=+1 E8 1160 REM 854 Y=+1 C8 1170 REM 855 ABOVE B1 B4 1180 REM 857 A: 81 C9 51 1190 REM 859 <> DO 01 1200 REM 861 COUNT E8 1210 REM 862 Y=+1 C8 1220 REM 863 NE B1 B4 1230 REM 865 A:81 C9 51 1240 REM 867 <> DO 01 1250 REM 869 E8 1260 REM 870 Y=40 AO 28 1270 REM 872 WEST B1 B4 1280 REM 874 C9 51 1290 REM 876 DO 01 1300 REM 878 E8 1310 REM 879 Y=42 AO 2A 1320 REM 881 EAST B1 B4 1330 REM 883 C9 51 1340 REM 885 DO 01 1350 REM 887 EB 1360 REM 888 Y=80 AO so 1370 REM 890 sw B1 B4 1380 REM 892 C9 51 1390 REM 894 DO 01 1400 REM 896 E8 1410 REM 897 Y=+1 C8 1420 REM 898 SOUTH B1 B4 1430 REM 900 C9 51 1440 REM 902 DO 01 1450 REM 904 E8 1460 REM 905 C8 1470 REM 906 B1 B4 1480 REM 908 C9 51 1490 REM 910 DO 01 1500 REM 912 E8 1505 REM 913 Y=X 8A A8 1510 REM 915 A=O A9 00 1520 REM 917 TO FP 4C 78 D2

Fi9:ure 30. Machine 1angua9:e 'count nei9:hbours'

35

Page 41: The Alien, Numbereater and other Programs for Personal Computers

The PET USR function allows one to transfer control out of the BASIC in­terpreter and directly to machine code, typically located away from the main areas of BASIC text and variables in, for example, the area used as a buffer for tl1e second tape cassette - memory positions 826 to 1017. USR is followed by a floating point number which can be used by the machine language program. In this case we pass the current cell screen address L% to it, but via Q, since USR expects a floating point, not an integer variable. After the machine language program has run, it can put a result in the same floating point variable, which is then avail­able to the BASIC program. In this case the value is the count, next transferred to K%.

To follow the program requires some knowledge of the fundamental lang­uage of the PET's internal microprocessor, the 6502. In this language, we cannot give BASIC - like instructions such as

A = 3.85 * SIN(PHI)

but are restricted to simpler ones like

LDA SAM ADC US

where the value 15 is added to the variable SAM - in microseconds, much faster than in BASIC, but with the drawback that the value added cannot exceed 255 and addition and subtraction is ~~e only computation allow­ed: if you want to multiply or divide, let alone do trig functions, you have to use subroutines built up from addition and subtraction. In fact these routines exist and are used by BASIC, so that when the arithmetic within a machine language program gets involved you can use those avail­able to the BASIC program. An alternative to USR is SYS, which does not pass a parameter to and from the machine code via thiR floating point 'accumulator', but would normally communicate with the main program by shared bytes of memory, loaded and tested by the BASIC program witl1 POKE and PEEK.

The REMs in 1000 - 1520 simply record the machine language program. At 1010, the variable L% ( actually it is Q by now ) is converted back into integer form by the obscure instruction

20 A7 DO

20 is the hexadecimal code for a single byte ( eight bits ) instruction code on the 6502- it is a 'Jump to subroutine', and the A7 DO specifies where in memory this subroutine starts. It is already available in the PET's Read Only Memory - as was said earlier, BASIC needs such sub­routines itself. This one simply converts the floating point represent­ation of the number into integer format, and leaves it in two bytes, the most significant part of the number in B3 and the least, in B4.

The problem is to convert the current cell address, in the range 1 -1000, and split between the least and most significant bytes, into the address in memory which corresponds to the first neighbour, one up and to the left of the cell in question. We need to

add 32767 to get the right memory offset,

aubtract 40 to get the line number,

and subtract 1 to get the row.

36

Page 42: The Alien, Numbereater and other Programs for Personal Computers

This is a net 32726, which has to be applied to the two bytes as follows

add 214 to the least significant byte,

add (32726-214)/256 = 127 to the most significant byte.

This we now do, first clearing the carry and forcing binary arithmetic mode ( to be on the safe side ) before beginning.

The least significant byte B4 is adjusted and replaced at line 1060, and attention directed to the most significant byte B3. It is brought into the accumulator at 1080 and 7F ( hexadecimal for 127 ) added to it - plus, one must not forget, the carry, if any, from the addition on B4 just performed.

Now B4 and B3 point to the right memory location to refer to the screen position above and to the left of the current cell.

At 1100 and 1110 the X index register is cleared - it will be used to build up the count of neighbours, and the Y register is cleared too: we shall use indirect addressing on B4 and B3 now they have been adjusted.

At 1120 things get under way. The contents are brought into the accumu­lator, of that memory location whose address is found by taking the byte at B4 ( and by implication B3 ) , plus the Y register ( so far equal to zero). 1130 tests it for the value 81 which is the ASCII code for the blob-shape used to denote a cell. If it is not equal to this code, the operation at line 1150 is bypassed - incrementing the X register by one, an operation which takes only a single byte of program and two micro­seconds to carry out - now we are scoring over BASIC!

At 1160 Y is incremented so that the instruction at 1170 now affects the memory location corresponding to the cell directly above the origin­al one. Then the same test is carried out and X incremented if another cell is found as neighbour.

The same process goes on round the original cell, incrementing Y by 1,1, then setting it to 40, 42 - missing the case where it would refer to the original cell itself, and so on. Finally at 1505 the count is assembled in Y ( least significant byte ) and the accumulator ( most significant -in this case bound to be zero, hence the clear accumulator step 1510 ) • At 1520 a subroutine is called to transfer the count into the floating point accumulator ready to be picked up by BASIC. The entry is deliber­ately a Jump rather than a Jump to Subroutine, so that the Return From Subroutine which will be encountered, will take control back to BASIC.

This program works quite well but was very awkward to write and test. Several times during testing the PET 'locked up' because there was a catastrophic error which made it impossible for control to return to the BASIC interpreter in an orderly way.

The subroutine to load the program into memory begins at 1530. The message is important: if it is omitted, the experimenter will get anx­ious when the PET seems to go dead. An alternative is to print the code as it is loaded, so that something is obviously happening. 826 TO 919 are the locations in the second cassette buffer where the program is to go. READ A$ picks up one byte from the DATA statements at 1610 follmv-­ing. J is the most significant character and is converted from hexadec­imal to decimal at 1540/1550. The second half is K. The complete dec-

37

Page 43: The Alien, Numbereater and other Programs for Personal Computers

1525 REM 1527 REM-------LOADER 1528 REM 1530 PRINT"3JUST A MOMENT - LOADING 1535 FOR I=826 TO 919 1540 READ A$: J=ASC(A$)-48 1550 IF J>9 THEN J=J-7 1560 K=ASC(RIGHT$(A$,1))-48 1570 IF K>9 THEN K=K-7· 1575 L=l6*J+K 1590 POKE I,L: NEXT 1600 POKE 1,58: POKE 2,3 1610 DATA 20,A7,DO,A5,B4,18,D8,69,D6 1620 DATA 85,B4,A5,B3,69,7F,85,B5,A2 1630 DATA OO,AO,OO,Bl,B4,C9,51,DO,Ol 1640 DATA E8,C8,Bl,B4,C9,51,DO,Ol 1650 DATA E8,C8,Bl,B4,C9,51,DO,Ol 1660 DATA E8,A0,28,Bl,B4,C9,51,DO,Ol 1670 DATA E8,A0,2A,Bl,B4,C9,51,DO,Ol 1680 DATA E8,A0,50,Bl,B4,C9,51,DO,Ol 1690 DATA E8,C8,Bl,B4,C9,51,DO,Ol,E8 1700 DATA C8,Bl,B4,C9,5l,DO,Ol,E8,8A,A8 1705 DATA A9,00 1710 DATA 4C,78,D2 1730 RETURN

Figure 31. Machine language loading from DATA

imal value is built up at POKEd into the right place at 1590. The POKEs at 1600 are required to arrange a safe return to BASIC from the machine code. I think we have now covered all the ins and outs of SUPERLIFE: so,

THATS ALL, FOLKS !

38

Page 44: The Alien, Numbereater and other Programs for Personal Computers

Double Density Histograms Techniques: use of special graphics characters, Assembler program­ming.

This short program is one example of the use of 'double density plots' on the PET screen, which in turn relies on a program written for the 6502 microprocessor in Assembler. In Superlife, the machine code pro­gram was hand-coded: here, it was written using mnemonic operators like ADC ( for Add with Carry ) instead of hexadecimal codes like 6D, and symbolic names like FRED instead of hexadecimal addresses like E56A, re­lying on an Assembler to translate from the meaningful to the obscure.

Try running the program and see results like Figure 32 - a histogram with almost 80 columns, instead of the 40 one would expect with a 40 character width screen.

Now consider Figure 33, the program itself. Subroutine 900 loads the 6502 code, stored, as hexadecimal, in the DATA statements 1000 - 1005. It works by picking up pairs of hexadecimal characters in N$, drawn out of the current DATA statement by MID$. N$ is split into two parts in line 930 and 935 and each part, as M$, is converted into a number 0 - 15 and built up into a byte value 0 - 255 in W: then W is loaded into an area of PET memory - the second cassette buffer - which is usually free.

This area extends from 826 to 1017, and after the machine code is set up there, a BASIC program can dive into it by executing SYS 826. In this case, the function of the machine code will be explained in more detail later, but briefly, it places a character on the screen drawn from the 16 PET graphics symbols which represent all the combinations of a square divided into quarters with any quarter black or white

! ! ! ! ! ! ! ! ! l ! ! ! ! ! ! ! ! l ! ! ! ! ! l

The above enlarged character effectively plots a mini-character of half width and height in the top right. Of course, the machine code program has to be told which sub square to plot, and then has to check '"hich big character to write on the screen so as to include both the subsquare specified, and any pre-existing subsquares.

At line 3, the '3' printed is in fact a 'reverse heart' to clear the screen. Line 4 writes a scale down the right hand side - the ' + ' is, again, the special PET character like a little T rotated 90 degrees anti-clockwise.

Line 6 completes the loop and sets the first Y plot to 10. Line 7 does all the work: LX ( 'large X' ) advances across the bottom of the screen printing, not '1', but an upside down T. X advances at twice the rate of LX and at each increment a GOSUB to 400 arranges for a vertical hist­ogram line to be drawn. After tracking across, •'le write a title and go into a loop to stop tl1e histogram being spoiled by a READY.

Subroutine 400 works out the height of the bar to be drawn by taking YL

39

Page 45: The Alien, Numbereater and other Programs for Personal Computers

Figure 32. Results of Double Density Histogram

0 REM-----------DOUBLE DENSITY HISTOGRAMS--------1 REM 2 PRINT" 3" : GOSUB 9 00: REM CLEAR SCREEN & LOAD M/C CODE

4 FOR Y=l TO 23: REM DRAW SCALE, DOWN Y AXIS 5 PRINT"+" 6 NEXT: YL=10: REM SET START VALUE FOR COLUMN

7 PRINT"-"; 8 FOR LX=2 TO 39:PRINT"l";: X=LX+LX: GOSUB 400: X=X+l: GOSUB 400: NEXT

10 PRINT:PRINT TAB(18);"11G 0 L D 1980 2" 20 GOTO 20: REM DON'T SPOIL GRAPH WITH READY

397 REM 398 REM--------DRAW COLUMN UP GRAPH 399 REM 400 YL=YL+ 3*XA.25 * (.5-RND(5)): FOR Y=3 TO YL+5 :GOSUB 600: NEXT: RETURN

600 POKE 839,X: POKE 840,Y 610 SYS 826: RETURN 897 REM 898 REM-------- LOAD M/C CODE 899 REM 900 L=826: FOR I=1 TO 5: READ A$ 910 FOR J=1 TO LEN(A$)/2: K=J+J-1 920 N$=MID$(A$,K,2) 930 M$=LEFT$(N$,1): GOSUB 950: W=V*16 935 M$=RIGHT$(N$,1): GOSUB 950: W=W+V:PRINT L,W:POKEL,W 937 L=L+1: NEXTJ,I: RETURN 947 REM 948 REM----------HEX TO DECIMAL 949 REM 950 V=ASC(M$)-48: IF V>9 THEN V=V-7 955 RETURN 997 REM 998 REM--------M/C CODE TO DRAW/ERASE A LITTLE SQUARE 999 REM 1000 DATA 205B03208C0320A70320B9036037130303207E7CE27B61FFEC6C7FE1FB62FCFE

1001 DATA AOCE4703AD470329018D49034E4703A9018D4A0338A932ED48038D48032901FO

1002 DATA 05A9048D4A03AD4903F0030E4A034E480360AD48030AOA186D480385BOA90006

1003 DATA B02A06B02A06B02A098085B160AC4703B1BOA200DD4B03FOOSE8E010DOF6608A

1004 DATA 0D4A03AABD4B0391B060202020202020525453220D22203732202E454E44220D

1005 DATA 0000

Figure 33. Double Density Histogram program

40

Page 46: The Alien, Numbereater and other Programs for Personal Computers

and increasing it by an increment which is partly random, but partly increases with X, so that the histogr~~ gets taller, on average, to the right - it looks more optimistic that way.

Note the use of (.5 - RND(5}} to provide a factor in the range -.5 to .5 and the exponentiation of X by a fraction to give factors in the range

.25 1 1, to

.25 79 .298

In line 600 the small square coordinates are stored in two locations in the second cassette buffer, and the machine code entered with SYS.

So the program as a whole is fairly simple, and could obviously be used to draw genuine histograms for real applications. Much of the interest lies in the Asssembler instructions, which are given in Figure 34. They are the mnemonics which are translated into the gobbledy-gook in the DATA statements.

Line 1 of Figure 34 specifies the program is to begin at hexadecimal 033A (the S signifies hex). 033A is decimal 826, in fact:

03 X 256 + 16 X 3 + A 826

( A is hexadecimal for 10 ) •

In lines 2 - 4 the main logic of the program is given: NORMalise is to calculate the coordinates LX,LY of the big square in which the little square lies, and the little square's coordinates within it- 0.0 TO 1,1. How this is done is explained later. CALC works out the screen location of the big square LX,LY and GET takes the character already there, and tests it to see which, if any, of the 16 special characters it is. PUT places into the position just found, a character which contains both the subsquares in the existing character and the newly required subsquare.

Now for a little more detail. The BYTE. statements reserve memory for the working variables including the X,Y planted by the main program in LX, LY, and a value DRAW which controls whether the routine is to add the specified subsquare to the screen or expunge it. LO and HI are com­ponents of the memory address to be computed. You will note that values were put in the BYTE. statements although one would expect them to be unassigned: this was done for testing the subroutine elements NORM etc independently.

In NORM, line 22 DECrements LX by one to bring it into the range 0 - 79, then fetches it and extracts the last bit by AND #1: this is the X co­ordinate of the subsquare within the big square, and is stored from the accumulator ( STA ) in sx. LX is Logically Shifted Right ( LSR ) at line 26 to effect a division by two, leaving it as the big square X co­ordinate. Y is presented to this routine in the conventional graph way with Y high at the top of the screen: since the memory address works the other way, with low screen addresses at the top, we have to reorgan­ise Y in lines 27 following. First the Y subsquare component SY is set to 1: we may change this later if Y is not even. In 29 we Set Carry be­fore doing a subtraction. It would take too long here to explain the reasons for clearing carry before addition, and setting it before subtr­action: it is a consequence of the 6502's method of subtracting through forming the complement of the subtrahend and then adding. I suggest you read a textbook such as Zaks, 'Programming the 6502', Sybex 78, or a broader approach, Duncan, 'Microprocessor Programming and Software Devel­opment', Prentice-Hall, 79.

41

Page 47: The Alien, Numbereater and other Programs for Personal Computers

1 2 3 4 5 6 7LX SLY 9DRAW

lOS X llSY 12LO 13HI 14TABLE 15 16 17 18 19 20 21 22NORM 23 24 25-26 27 28 29 30 31 32 33 34 35 36 37NOY 38 39 40NOX 41 4

* = $033A JSR NORM JSR CALC JSR GET JSR PUT RTS

.BYTE 55

.BYTE 19

.BYTE 1

.BYTE 3

.BYTE 3 = 176 = 177 .BYTE 32,126 .BYTE 124,226 .BYTE 123,97 .BYTE 255,236 .BYTE 108,127 .BYTE 225,251 .BYTE 98,252 .BYTE 254,160

DEC LX !0-79 LDA LX AND U STA SX !SUBSQUARE LSR LX !0-38 LDA #1 STA SY !MINIMUM SEC LDA #50 SBC LY ! 0-4r9 STA LY AND # 1 ! REMAINDER BEQ NOY LDA i4 STA SY LDA SX BEQ NOX ASL SY !2 OR 8 LSR LY !0-2•4 RTS

42CALC 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 GET 59 60 61LOOPA 62 63 64 65 66 67FOUND 68PUT 69 70 71 72 73ERASE 74 75 76 77 78SETX 79 80 81 82 .END

LDA Y ASL A !X2 ASLA CLC ADC LY !LY X 5 STA LO LDA #0 ASL LO ROL A ASL LO ROL A ASLLO ROL A ORA #128 !HI 32768 STA HI RTS LDY LX LDA (LO) ,Y LDX itO CMP TABLE,X BEQ FOUND INX CPX. tl6 BNE LOOPA LDX tO RTS LDA DRAW BEQ ERASE TXA OPA SY JMP SETX LDA SY EOR #%11111111 STA SY TXA AND SY TAX LDA TABLE,X STA (LO), Y RTS

Figure 34. Asserohler program to plot/erase

small squares

Note. The double density histogram program only plots and does not erase, so the parameter DRAW is set at 1 ( i.e. draw not erase ) and is not amended by the BASIC program.

42

Page 48: The Alien, Numbereater and other Programs for Personal Computers

At 31 the Y value is turned round to fit the screen and the low order bit extracted and used to decide whether the decision to set the sub­square SY to 1 was right- if not, it is changed to 4. At 37, it is modified further in the light of the contents of SX: doubled, in fact, if SX is non-zero, by ASL.

SY now contains 1 if Y was even and so was X: 2 if Y was even and X was odd, 4 if Y was odd and X even, 8 if both were odd. It is in effect the position within the square now indicated by LX,LY where a little square is to be added, if we number the little squares within the big square:

Big Square LX,LY: 4 8

Subsquare within ----!

it 1 SY = (1 , 2 , 4 , 8} ! 1 2

NORM has thus converted LX and LY into values we can use to manipulate numbers which can be formed into a single screen address, in the range 32768 - 33767. To do this, LY has to be multiplied by 40, the number of bytes in a screen line. For speed, we do this by a trick. LY is multi­plied by four through the two Arithmetic Shift r.efts in 43,44 and the original LY added in, effectively multiplying by 5. The result is shif­ted left three times, with ASLs on a variable LO, while the accumulator A is ROtated Left ~icking up any carries that emerge from the high end of LO, so that at 54, A and LO between them contain 8 x 5 x the original LY. The offset 32768 is added by using ORA rather than ADC ( to save a CLear Carry, since '"'e know that the bit to be pushed into A will go into a zero bit position, so OR is as good as ADD. The accumulator finally goes into HI, so HI,LO make up an address into screen memory. We have not dealt with the LX value, though.

Routine GET tackles the extraction of the byte in the screen by loading index register Y ( it is confusing that this register is called Y in this situation, since it is the x axis we are dealing with } with LX, then using indirect addressing in line 59 to bring into the accumulator that byte which is pointed to by HI,LO plus the contents of Y, i.e. the x axis offset. Now, is the byte we have got, one of the special PET 'little squares' symbols? This is found by scanning the TABLE of 16 PET symbol codes and testing for a match. Note how a different style of indexing is used here - the value obtained is directly from TABLE, not from some other byte pointed to by a value in TABLE: however, it may be the first, second, •• ,fifteenth byte of TABLE depending on whether X is 0 or 1 or ••• 15. If all tests fail there is a byte in the screen which is not a blank and not one of the 'little squares' symbols, in which case X, which shows the point in the loop when a match was found between the accumulator A and a TABLE hyte, is reset to zero so that the rogue byte is treated as if it was the first TABLE entry - a blank/space.

The last subroutine PUT is responsible for replacing the byte just found with a new byte, whose code is such that it appears like the old one but

43

Page 49: The Alien, Numbereater and other Programs for Personal Computers

with a little square added in the appropriate subsquare quadrant. Or, if the whole program is called with the parameter DRAW set to zero, the opposite is to be done - the little square is to be effectively removed.

If we are to DRAW then we continue with 70, fetching the index register X into the accumulator and ORing it with the subsquare code SY ( the Y in SY is now rather misleading since SY incorporates the information in SX now and is a complete specification of the subsquare we want to add.) By ORing them together we have generated the address in TABLE where the required new byte is to be found, and we can JuMP to the code SETX where this byte is to be placed in screen memory.

If DRAW had been zero, we need to do the opposite, and remove the bit in X which matches the bit in SY, i.e. if we treat them as logical strings the result is to be

X AND NOT SY

for example 00001010 AND NOT 00000100 leaves X unchanged since the bit in SY which should remove any bit in the same position in x, has no such correspondng position and thus has no effect: the byte concerned is al­ready without a subsquare in quadrant SY. ( This is why it would have been wrong to use arithmetic here - a subtraction would have upset X. )

At SETX, the byte in TABLE to replace the old one ( it may well be the same, in fact ) is loaded at 79 and pushed into the screen area in 80, using the converse of step 59. Then back we go to the main logic and at 6, back to the calling BASIC line.

Note that the program could be rewritten to avoid the 'unnecessary' use of subroutines in 2 - 6, but it is clearer to write and easier to test as it is.

The routine just described will be used in SPIRAL WORM which follows.

It is suggested that you can use this program as a convenient way of presenting statistical information by incorporating it as a subroutine in your domestic budgeting system, and capture the results with a camera at a slow shutter speed setting. Actually, I have my tongue in my cheek when I say that: domestic computers are unpopular enough with the rest of the household without them intruding on the sensitive issue of family finances. But for a small business wanting to see which way sales are going it could be quite neat.

44

Page 50: The Alien, Numbereater and other Programs for Personal Computers

Spiral Program Techniques: edge detection (pattern recognition), random walks, heuristics, integer variables.

This program was written to exploit the machine code program described in 'Double Density Histograms' in a new application: the simulation of a little worm-like creature that obeys the following rules:

Initially it moves in a randomly chosen direction, dragging its tail behind.

If it hits a + sign, it immediately goes into a tight right-hand spiral, which gradually enlarges.

If it hits the side of the VDU, it reverses direction and goes into random mode again.

The effect of these rules is that the worm wanders about the screen re­bounding off the sides until it meets a'+', when it 'searches' in the area for more of them, giving the impression of following any trail of '+' symbols, including bends and corners. If it loses contact with them, it spirals outwards until it finally finds another +, or hits a side, when it resumes aimless wandering.

The technique of tightening one's search when 'warm' and expanding it when 'cold' is used by sailplanes to find thermals and by pattern recog­nition devices for tracking the limbs of characters. One can also det­ect similarities with the behaviour of simple animals like ants ( a nice exercise would be to design a program with several worms, each given the power of communicating to another worm the direction and distance away of the nourishing + signs. It is certainly entertaining to watch though rather slow-running. See Figure 38 for a photograph of the screen while SPIRAL WORM was run.

But also look at the program, Figures 35 - 37. ~t 200 we load machine code as in Histograms, although the INIT section at 200 following first initialises some variables. X% and Y% are the screen position of the worm's head. P is a pointer for the worm's tail plots, A is the worm's initial direction-octant: it cannot have the 'integer' suffix '%' since it can actually take values like 5.293. FOUND% is a flag which is set when the worm finds a '+' and reset when it hits a side. In 201 the PRINT '3' ( actually 'reverse-heart' ) clears the screen. In 205-208 we initialise a number of integer variables we shall use as constants. The PET runs faster if it has a variable name rather than a constant, which it has to convert from characters to binary each time it meets it. Time is also saved by specifying variables as integers if they never take fractional values ( but not space - in fact using variable names that have the extra% ,slightly increases the memory used). I have followed the convention of calling them by I followed by the digit value they represent. One is tempted to use Roman numerals, but VII% will be taken as equivalent to VI%. Similarly constants greater than 9 cannot follow this rule since IS% and !51% would be equivalent to the PET.

In 208 similarly some constants are set up for the procedure in line 700 for converting the x,y position of the worm's head into a PET memory address. 210 - 230 load the delta x,y increments corresponding to a move in each octant. Since all the arrays in this program have less than 11 elements there is no need to DIMension them ( though there is a case for doing so for clarity). At 240 a test is made to see if the machine code program appears to be already in the second cassette buffer, by looking at one typical byte. There is in fact a 1/256 risk that when the PET is switched on this byte will contain this value anyway: the result would be a crash after SYS was attempted. So this technique, for avoiding a needless load when the experimenter is bored with a run and wants to start again, would be un­acceptable in a commercial or industrial system where a 1/256 chance of

Page 51: The Alien, Numbereater and other Programs for Personal Computers

0 REM-----------SPIRAL WORJ:ol-------1 REM 2 GOSUB 200: 3 GOSUB 3000: 4 D=RND(6): IF D<PE THEN 10: 6 IF D<PN THEN A=A+IO%: 7 IF D>PN THEN A=A-Il% 10 IF A<IO% THEN A=A+I8% 12 IF A=>I8% THEN A=A-I8% 14 DX%=AX%(A): DY%=AY%(A): 30 X%=X%+DX%: Y%=Y%+DY%: IF X%>IO% AND 35 FOUND%=IO%: A=A-I4%: GOTO 10: 40 GOSUB 700

REM LOAD CODE REM LOAD SHAPE REM DIRECTION AT RANDOM. PE=.8 REM CHOOSE OCTANT TO GO IN: PN=. 9

REM INCREMENT IN POSITION OF HEAD X%<MX% AND Y%>Z% AND Y%<MY% THEN 40

REM HIT SIDE, TURN BACK

45 GOSUB 500: IF V%=PL% 46 IF FOUND%=IO% THEN 4 47 A=A+L: L=L*R :

THEN FOUND%=Il%: L=Il%: REM PLOT WORM, TURN IF + REM CARRY ON WITH RANDOM REM SPIRAL OUTWARDS

50 GOTO 10 197 REM 198 REM------------INIT 199 REM 200 X%=70: Y%=5: P=l: A=3: 201 FOUND%= 0 : PRINT "3" 205 PE=.8: PN=.9: I0%=0: Il%=1: I4%=4: 206 !8%=8: MX%=81: MY%=51: PL%=43 208 VD=33767.5: H=.S: LX=40 210 FOR I=O TO 7: READ AX%(!): NEXT: 215 FOR I=O TO 7: READ AY%(I): NEXT 220 DATA -l,-1,-l,O,l,l,l,O 230 DATA -1,0,1,1,1,0,-1,-1 240 IF PEEK(827)<>92 THEN GOSUB 900: 250 RETURN

REM WORM X,Y,TAIL-PTR,START DIR

IS%=5: I7%=7: R=. 97

REM LOAD TABLE TO FIND DX,DY

REM LOAD M/C CODE IF NOT THERE

Figure 35. SPIRAL WORM main logic and INIT

499 REM------------PLOT WORM 500 REM 505 PX%=SX%(P): PY%=SY%(P): D%=IO%: GOSUB 600: REM ERASE END OF TAIL 510 PX%=X%: PY%=Y%: D%=Il%: GOSUB 600: REM ADD HEAD 520 P=P+Il%: IF P>IS% THEN P=IO%: REM MAINTAIN CIRCULAR TAIL-LIST 530 Q=P-Il%: IF Q<IO% THEN Q=IS% 535 SX%(Q)=X%: SY%(Q)=Y%:D%=Il%: REM SAVE HEAD IN TAIL LIST 540 RETURN 600 POKE 839,PX%: POKE 840,PY%: POKE 84l,D%:REM SET UP X,Y AND DRAW/ERASE 610 SYS 826:RETURN 700 W=VD+X%*H -LX*INT(H +Y%*H): V%=PEEK(W): REM WHERE HEAD IS 702 RETURN

Figure 36. PLOT, USE SYS, FIND WHERE HEAD IS

46

Page 52: The Alien, Numbereater and other Programs for Personal Computers

a program crash would be too high. Other approaches are also tricky. We cannot set a 'Program Loaded' flag because when RUN is keyed in, all variables and flags are reset. A more radical approach would be to pis­able the RUN/STOP key and build into the program a test for a key de­pression to indicate the experimenter wanted to restart.

In line 3 subroutine 3000 is used to set up ( Figure 37 ) a shape for the worm to operate on.

In line 4 following, the direction octant A is modified clockwise or anticlockwise according to the random number D: most of the time, when D<.8, the worm keeps on in the original direction. Lines 10 and 11 arr­ange for A to pass through 'north' keeping in the range 0- 7.99999 ••• ( the fractional part arises when spiralling later: it is ignored when A is used as an index in line 14 to get the direction increments along the x,y axes which are appropriate to this octant). Line 30 revises the coordinates of the worm's head and checks to see if the head is outside the boundaries of the VDU, taken as 80 x 50 sub­squares. If it is, line 35 sets the worm into wandering mode and turns it through 180 degrees. But if all is well, line 40 uses 700 to find where the head is in PET memory terms and to extract the character there in the hopes we have found an edible '+'. At 45 the PLOT WORM routine is used to advance the creature's head and cut off the end of its tail. If we were lucky enough to find a '+' then the state FOUND% is recorded and the worm goes into a tight right-hand turn, which it will maintain, but gradually expand it, so long as no more '+'s are found, rather than employing the random number D to wander as before. Note that PET BASIC allows several statements in an IF. All are taken if the IF succeeds. Finally consider the detail of the PLOT WORM procedure in 499 following. SX%() and SY%() hold the subsquare coordinates of ·the five elements of the worm, so we first cut off the tail, specified by the coordinates of the current end-of-circular-list, pointed to by P ( which has to be a 'real' rather than an 'integer' variable because the PET demands this of variables used as subscripts, rather annoyingly). These coordinates are passed as PX% and PY% to subroutine 600 which actually calls the machine code, together with the parameter D% set to zero to show that an erase, not a draw, is called for. The subroutine is called again in draw mode, to plot the new head, and at 520 the circular-list pointer is incremented by 1 but 'wrapped round' through 5 if it has gone too far. It now points to the new tail element which was previously the last but one before the old end was cut off. The head must now be put into the list since it will soon become the 'neck' when the next head is plotted. We produce a pointer Q for this purpose, calculating it as the 'other s ide' of the tail pointer P •

. I . .

Q I I

p

This technique is very useful for maintaining stacks data is added on one end and removed from the other. pointer is better than incurring a lot of processing ing the data down.

of information when Using a rotating

time actually mov-

Line 600 demonstrates how one transfers parameters with quasi-symbolic names like PX% ( for 'Plot coordinate x ) into the bytes in memory where the machine code refers to them.

700 is an obscure little calculation which uses VD as the address at the

47

Page 53: The Alien, Numbereater and other Programs for Personal Computers

897 REM 898 REM-------- LOAD------899 REM 900 L=826: FOR I=l TO 5: READ A$ 910 FOR J=1 TO LEN(A$)/2: K=J+J-1 920 N$=MID$(A$,K,2): REM A HEX CHARACTER 930 l-1$=LEFT$ (N$, 1): GOSUB 950: W=V*16 935 .f.l$=RIGHT$(N$,1) :GOSUB 950: W=W+V: PRINT L,\h POKE L,W 937 L=L+1: NEXT J,I: RETURN 950 V=ASC(M$)-48: IF V>9 THEN V=V-7 955 RETURl~ 1000 DATA 205C03208D0320A80320BC03601BOF010004207E7CE27B61FFEC6C7FElFB62FC 1001 DATA FEAOCE4703AD470329018D4A034E4703A9018D4B0338A932ED48038D4803290l 1002 DATA F005A9048D4B03AD4A03F0030E4B034E480360AD48030AOA186D480385BOA900 1003 DATA 06B02A06B02A06B02A098085B160AC4703B1BOA200DD4C03F007E8E010DOF6A2 1004 DATA 0060AD4903F0078AOD4B034CD403AD4B0349FF8D4B038A2D4B03AABD4C0391B060 1005 DATA 0000 3000 PRINT "3" 3010 PRINT ++++++++++++ 3020 PRINT + + 3030 PRINT + + 3040 PRINT + + 3050 PRINT + + 30 60 PRINT + + 3070 PRINT + + 3080 PRINT + + 3090 PRINT ++++++++++++ 3095 FORI=1 TO 160:PRINT"+";:NEXT 3099 RETURN

READY. Figure 37. Load machine code ( same as Figure 33 )

and set up worm's task

............ + + + + + + +

+

..........•. +++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++ ++ ++++++++++••++++++++++++++++++++ ( + + +. + + ++ + +++ +++++++++++++++++++

Figure 38. Worm at work

48

Page 54: The Alien, Numbereater and other Programs for Personal Computers

end of the PET VDU memory block, plus .5 to round it off. square value x is added, and 40 times the rounded y value It took a long time to get this right! Note the use of H calculation, rather than using the more obvious X/2.

Half the sub­subtracted. to speed the

It is interesting to think about the performance of the spiral worm a little more deeply. It is reasonably successful in demolishing connect­ed structures provided they do not bifurcate, when it follows one branch and does not remember to go back to the junction when it finally runs out of '+'s: it re-acquires the succulent food only by wandering at random. we may consider this strategy rather puerile. But consider a greenfly, which works no more intelligently. When it is lucky enough to be on a plant, it keeps eating and moving and since the plant is liter­ally a tree structure ( which is to computer scientists, upside down 1 ) it will find its way back to a junction after eating one branch. It has the advantage over the worm of having the skeletal remains of the plant to guide it back to lower junctions: it need have no memory, only the habit of eating and moving where food is. When it finds no more plant, it should launch into the air at random ( in nature plants do not grow in rows). So simple a strategy saves the cost of providing the green­fly with the more complex brain and behaviour patterns of, say, a spider or a wasp.

A nice project would be to amend SPIRAL WORM so that the worm alters the '+'s into some other symbol when it has 'eaten' them, and for the algor­ithm to get it to track these 'dead wood' symbols as being better than nothing, when out of '+'s. When the worm is presented with connected structures, this should cause it to backtrack nicely, like a greenfly.

However, there may be other situations: if we scatter '+'s around the screen singly, at random, the inbuilt instincts of the worm do not give it much advantage over pure random motion, as is characterised by, say, small crustacea in the sea living off the fairly randomly distribut-ed plankton. One can philosophise further. Modern programming practice demands that a program accurately reflects the structure of the data with which i·t deals. Every problem has a structure ,.,ith definite rules and our task as analysts is to find them and eventually devise a suit­able processing algoritP~ ( which can itself be expressed as a graph or state transition diagram ) \lhich reflects the rules by which its target data structure is constructed. It does not have to be as complex, since by recursion ( see BRACKETS and PALINDROME ) it can elaborate its behav­iour to match even the most baroque implementation of the data structure just as a greenfly given time, and assuming it did not reproduce, can strip the biggest runner bean in the world.

49

Page 55: The Alien, Numbereater and other Programs for Personal Computers

Fternover Techniques: Assembler programming, documentation.

Virtue may be its own reward, and the sight of a program beautifully set out with spaces inserted thoughtfully, e.g.

FOR I=l TO 100

rather than

FORI=lTOlOO

and lots of P~Ms made to stand out with dashes, e.g.

REM------------------- AREN'T I A GOOD BOY ? ---------------

make us glow with pride. Then, when we come to run the program, we find we get the message

OUT OF MEMORY

and we miserably remove all the REMs and spaces to make room for vari­ables, leaving the program impossible to understand. Perhaps we could maintain two versions, one legible but inoperative, the other operative but illegible? Unfortunately few of us are patient enough to maintain both versions.

Since my PET has only 8K this is a severe problem for me, and so I felt it would be very useful if I could load a 'good' program and strip it of REMs and spaces immediately before execution, thus freeing more space for variables. Program debugging would be done on small size arrays, and if errors occurred later, I would reload the 'good' program, amend it and save it. Never, I swear, would I amend the reduced, commentless version!

How are the spaces and REMs to be removed when the program is sitting in memory ready for execution? I started by writing a BASIC program which examines itself, using the following facts:

Each BASIC statement consists of two bytes pointing to its success­or, two bytes of statement number, a variable number of bytes of text, in which many key words are reduced to one or two bytes, and ended with a zero byte.

The start of the BASIC program is pointed to by bytes 122 and 123, i.e. PEEK(l22) + 256*PEEK(l23) gives the address of the first line, which is normally 1025.

Variables are allocated memory after the end of the ~rogram, which is indicated by the presence of two zeros in the position where one normally expects the link address in a new BASIC statement. The start address for the variables area is found in bytes 124 and 125.

I found writing this BASIC program very difficult because if it failed, it was very likely to corrupt the progra~ itself. l~oreover, even when working properly, it would condense itself so that the BASIC interpreter was trying to deal with a program that was shrinking under it, and would thus lose track of where it was in the statement. So I had to write the program ( Figure 39 ) in a condensed form so that its operations on itself would be ineffective. But this of course made it hard to read, and naturally it was not now tested except in the weak sense that one showed it did no harm to the program, but not that it did any good!

50

Page 56: The Alien, Numbereater and other Programs for Personal Computers

10000 OP=l025:NP=OP 10030 FORI=OT03:X=PEEK(OP+I) :POKENP+I,X:NEXT:I=4:J=4 10050 A=PEEK(OP+I) :PRINTCHR$(A)~ 10060 IFA=34THEN10200 10065 IFA=OTHEN10100 10070 IFA=32THEN10095 10075 POKENP+J,A:J=J+l 10080 IFA=143THEN10300 10095 I=I+1:GOT010050 10100 POKENP+J,A:OP=PEEK(NP)+256*PEEK(NP+1) 10110 NJ=NP+J+1:NH=INT(NJ/256) :POKENP,NJ-256*NH:POKENP+l,NH:NP=NJ 10115 IFPEEK(OP)+PEEK(OP+l)<>OTHEN10030 10117 POKENJ,O:POKENJ+1,0:STOP 10200 POKENP+J,A:I=I+1:J=J+1 10210 A=PEEK(OP+I) :IFA=0THEN10100 10220 IFA=34THEN10075 10230 GOT010200 10300 I=I+1:A=PEEK(OP+I):IFA=OTHEN10100 10310 GOT010300 15000 J=l025 16000 PRINTJ,PEEK(J)+256*PEEK(J+1)~PEEK(J+2)+256*PEEK(J+3)~" ••• "~:J=J+3 16010 J=J+1:IFPEEK(J)<>OTHEN16010 16020 PRINT"O":IFPEEK(J+1)<>0THENJ=J+l:GOT016000 16030 PRINT"OO":STOP 20000 T E S T DATA 20010 VVVVV: A=D: 20015 L L L L

READY.

A$=" ~ II

Figure 39. REMOVER, BASIC version for exper1ments. Quite unreadable.

10 DIM A$(214,10) 2 0 REM THE STRAW THAT BREAKS THE CAMEL 1 S BACK

READY. RUN

?OUT OF MEMORY ERROR IN 10 SYS 826

READY. LIST

10 DIMA$(214,10) 20 REM

READY. RUN

READY.

Figure 40. Results of REMOVER on a

BASIC program

51

Page 57: The Alien, Numbereater and other Programs for Personal Computers

Another maddening error which took days to find was the discovery that

POKE X,PEEK(Y)

did not work, and that one had to write

Z=PEEK(Y): POKE X,Z

Still, the program finally ran, and this tested the logic of the version to be coded in Assembler. It was clear that the final version could not be written in BASIC because

The program itself would take up space which it was supposed to save. It is perhaps feasible to consider gettinq it to transport itself to the other end of memory, loading the new program behind it, operating on it, and then resetting the pointers so that the interpreter would deal with the newly loaded and condensed program, and overwrite 'REMOVER' with variables. This stratagem seemed too terrifyingly difficult to contemplate and I decided to write the program in Assembler and tuck it into the second cassette buffer. This would have the additional advantage that so long as the PET is switched on, and the second cassette is not used, any program can have its fat trimmed off by REMOVER without any more program load­ing.

Lastly, I had in mind that REMOVER might be a marketable software product, and there is slightly more chance of preserving iL from piracy, if it is not in BASIC.

The translation of the prototype into Assembler took a long time, due to difficulties with the Assembler and Editor program I use, and partly through irritating faults in reading and writing cassettes.

At all events, the program now seems to work. See Figure 40, an example of results. A large array has ben DIM'ed, which, together with a rather long REM statement means that the PET has insufficient memory left. The experimenter keys in SYS 826 ( after, of course, loading REMOVER - a process to be discussed shortly). A LIST shows that the REM has been shortened. Note that REMOVER does not take out the whole line, because it may be that the line number is referred to in a GOTO or GOSUB else­where, and its total removal would cause an UNDEFINED STATEMENT error. You may say, why not try harder and build in a test for cross references so that you can safely remove the whole line? I can only say, you have a go!

When the little program is run again you can see that the removal of THE FEATHER* THAT BREAKS THE CAMEL'S BACK now allows it to run. Notice the use of FRE(O) to determine how much memory is left- it also reorganises it and disposes of any left-over variables not now required. Now it is time to examine the program itself, see Figure 41.

Much of this section is concerned with the problem of describing a pro­gram, and the various graphical techniques available, such as the time­honoured flow chart or ( as I prefer to call it to distinguish it from a system flow chart ) , the logic block diagram. I begin by using a method which is perhaps under-valued - a written description in English.

*it should really have been 'STRAW'.

52

Page 58: The Alien, Numbereater and other Programs for Personal Computers

1 2NJLO 3NJHI 40PLO 50PHI 6NPLO 7NPHI SI 9J

10 11 12 13 14 15 16 17REM 18 19 20 21QUOTE 22 23 24 25 26 27 28 29MOV4 30 31 32SHOV 33 34 35 36 37 38 39EXAM 40 41 42 43 44 45SAVE 46 47 48 49SKIP 50 51 NULL 52 53 54 55 56 57 58 59

* = $033A = 227 !TEMP POINTER = 228 1 TO NEW LINE = 229 !POINTER TO OLD

230 1 LINE OF BASIC = 124 !POINTER TO NEW = 125 1 LINE OF BASIC = 233 !INDEX IN OLD = 234 !INDEX IN NEW

LDA 12 2 1 POINTER TO STA OPLO !BASIC TEXT STA NPLO !INTO OLD LDA 123 lAND NEW LINE STA OPHI !POINTERS. STA NPHI Jl-1P ENDTST 1 CHECK NO TEXT INC I JSR GETOPI BEQ NULL BNE REM JSR PUTNPJ INC I INC J JSR GETOPI BEQ NULL CMP #34 BEQ SAVE BNE QUOTE LDA tO STA I STA J JSR GETOPI JSR PUTNPJ INC I INC J LDA I CMP t4 BNE SHOV JSR GETOPI BEQ NULL CMP t34 BEQ QUOTE CMP t32 BEQ SKIP JSR PUTNPJ INC J CMP U43 BEQ REI"'­INC I JMP EXAM JSR PUTNPJ LDY #0

lEND LINE 1 SKIP f.10RE. !STORE BYTE lEVEN IF lA SPACE, !UNTIL END !OF LINE OR !ANOTHER !QUOTE. !STILL INSIDE

!RESET BOTH 1 INDICES IXFR FOUR 1 BYTES 1 OLD TO 1 TO NEW

!GET OLD !LINE END !QUOTE?

!SPACE? !DONT XFR !COPY TO NEW IPTR IN NEW !REM !TO SKIP REST !PTR IN OLD !MORE OF LINE lEND MKR=O

LDA (NPLO),Y STA OPLO

!LINK ITO NEXT !OLD LINE !GOT BACK

INY LDA (NPLO) , Y STA OPHI SEC 1 + 1 LDA NPLO IPTR TO NEW

60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75ENDTST 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92EXIT 93GETOPI 94 95 96PUTNPJ 97 98

999 .END

ADC J STA NJLO LDA NPHI ADC tO STA NJHI LDA tO STA J LDA NJLO JSR PUTNPJ INC J LDA NJHI JSR PUTNPJ STA NPHI LDA NJLO STA NPLO LDA fO STA I STA J JSR GETOPI BNE MOV4 INC I JSR GETOPI BNE l10V4 JSR PUTNPJ INC J JSR PUTNPJ CLC LDA NPLO ADC #2 STA NPLO BCC EXIT INC NPHI RTS LDY I

1LINE,+J+1 !INDEX = PTR 1 TO NEXT NEW, !SAVED IN ITEMP'Y LOC. !INDEX TO INEW,RESET. !SAVE NEW !LINK IN PLACE !OF OLD.

lAND UPDATE lNEW-PTR TO 1 REFER TO NEXT I RESET !INDEX IN OLD lAND IN NEW. IEXAM START INEW LINE FOR !POSSIBLE END IOF BASIC TEXT !I.E. 00 !PUT 0 AT END lAND ANOTHER lONE. 1 FORM NEW PTR 1 OF VARIABLES

INO CARRY

LDA (OPLO) , Y RTS LDY J STA (NPLO),Y RTS

· 41 REMOVER program written in 6502 Assembler Fl.gure • _ _

Page 59: The Alien, Numbereater and other Programs for Personal Computers

REMOVER is located in the second cassette buffer, so in line 1 we direct the assembler to place the first instruction in hexadecimal ( the dollar sign ) 033A, and subsequent ones thereafter.

We now define some variables and allocate specific bytes in memory ( 227 etc ) where they are to be located. It is important to define them so exactly because they are in the 'zero page' of PET memory which contains many values which, if disturbed, will produce unpleasant consequences. By reading suitable PET manuals you will find out which are fairly safe to use. Zero page bytes are particularly useful because, firstly, the instructions which refer to them are a byte shorter in the address part than those referring to bytes elsewhere in memory - so the program is more compact, a fact which turned out to be important here. Secondly, you can use zero page bytes as indirect addressing locations, which is vital here. We choose locations which are used as work space by the PET interpreter so that disturbing them will not have any harmful results. NJLO is the LO order byte of a temporary address to be used by the pro­gram, and NJHI is the high order. For example, if NJLO contained 1 and NJHI 3, and index register Y was set at 5, then the instruction

LOA (NJLO),Y

would load the accumulator with the contents of the location pointed to by 1 + 256*3 + 5, i.e. the contents of 774.

OPLO and OPHI are the bytes making up the address pointing to the start of the Old line of BASIC text being examined, and NPLO, NPHI Point to the New line to which the old is being transferred ( less spaces and re­marks). These bytes are in fact the address of the start of the vari­ables area, so that when REMOVER has finished, we need only add 2 to them to form the correctly revised start-of-variables address. I and J are indices within the current old and new BASIC lines themselves.

What we shall be doing is moving old text a byte at a time into new text which in fact occupies the same space, but, since it is always shorter than the old, there is no risk of over-writing it. The only problem is that the link in the old line will be wrong since it will point to a line which is going to be moved, so the program has to go back and cor­rect it.

Now, at line 10 the program sets initial values for the start of old and new text, from the 'official' pointer in 122,123. LOA 122 is much simpler than the indirect example quoted above - it just loads the acc­umulator with the contents of 122. Then JMP ENOTST takes us deep into the program. Two interesting points should be discussed here. First, why is there a jump here - surely ENOTST might as well follow on immed­iately after STA NPHI? Secondly, in describing the program, are we to follow the sequence of the text or the sequence of control flow when the program runs?

Well, the reason for the jump is a disreputable one. At first, the pro­gram went straight on into MOV4 and SHOV which immediately followed at that time, and there was no test right at the start to see if there was any program there at all, with the result that a mistaken use of REMOVER with no program present would disable the PET until switched on and off again. ( REM and QUOTE at that time were positioned at the other end of the program and were resited here because the conditional branches BNE and BEQ etc had got too far from their target addresses - more than +127 or -128 bytes away).

So much for the rather arbitrary reasons for the positioning of these chunks of code. But, when describing a program, do we follow the text or the logic flow? I think the answer to this question depends on two

54

Page 60: The Alien, Numbereater and other Programs for Personal Computers

things - the ability of the program to describe itself at the tactical level, and the level of the supporting graphical description. In fact, I am beginning to think also about why I am describing the program at all - that is, what benefit you may gain from this section. It seems on reflection that the descriptive part will be of most help to you in get­ting an appreciation of the problems of writing Assembler, at a fairly tactical level, and the overall structure of REMOVER can be returned to when I discuss the graphical documents which should be better at reveal­ing the logic than the program itself. I have convinced myself the best way to describe the program now, is to go through line by line and not worry about overall logic yet. I hope you agree, and also see the need for this kind of dialogue with oneself when writing up a system for a third party to read.

So, at line 17 a little piece of code called REM occurs. It is entered when the character used by the PET to represent REM was found, and has the task of stepping through the remainder of the current line, by INC­crementing the pointer I, until a zero byte is found, indicating the end of the line. JSR GETOPI is a Jump to SubRoutine, called by me GETOPI to remind myself it GETs a byte of text pointed to by the Old pointer OPLO and OPHI, plus I. BEQ NULL tests the Z flag which will be set if the LDA in the subroutine brought into the accumulator a zero byte. Earlier I had had a CMP #0 ( compare with the constant zero ) before the BEQ, but realised it was not necessary. If the byte obtained from GETOPI was not zero, BEQ is not taken but the BNE NULL is. On the 6502 there is no unconditional branch: that is why it is necessary to use the complem­entary conditional branch.

At line 21 we find a similar piece of code entered when a byte is found equal to double quote, signalling the start of a text string, which has to be copied without the deletion of spaces, since they may be signif­icant. The double quote itself is copied into the new text through the use of the subroutine PUTNPJ, and then the pointers I and J, to old and new BASIC text, are incremented together and another byte of old text got with subroutine GETOPI ( I wondered why this subroutine reminded me of Alec Guinness - the reason is that he played the part of Kenobi in Star Wars). Again, if the byte is zero we go to the end-of-line code NULL, but if it is another quote we go back to the main loop having finished with the current text string. If it is neither a double quote nor a zero, we loop back to QUOTE and keep copying across.

MOV4 is used to transfer the first four bytes of an old line to a new, unchanged: two bytes of link address and two of statement number. The link address will have to be revised of course, but we need to preserve the old one to find where the next old line is to come from. So we might just as well preserve it temporarily here. First we set I and J to zero and fetch the first byte with GETOPI, and put in its new place with PUTNPJ. I and J are stepped up by one. What a convenience it iG to be able to increment bytes without having to bring them into the accumulat­or, otherwise we should have had to have written

CLC LDA I A::>C f.l STA I

!CLEAR CARRY

!ADD 1

T•iaybe things have improved since 19 60 after all.

But we still have to bring I into the accumulator, to test it for 4 at line 37 ( what '\ole are doing is the :z:>.ssembler version of FOR I=O TO 3 ) • When this loop is completed, control passes to the next piece of code,

55

Page 61: The Alien, Numbereater and other Programs for Personal Computers

EXAM, in which we process the line just begun. I and J are correctly set after the loop to point to the first byte after the statement number ( note that in BASIC it is dangerous to assume anything about the cont­rol variable of a FOR loop after the loop is terminated, but at this level of language we can see exactly what it will be). EXAM checks for line end ( BEQ NULL ) , and quote, taking the appropriate branch in each case. If it is a space, the BEQ SKIP bypasses the copying-across pro­cess at SAVE, and also avoids INC J ( so leaving the pointer into the new line unaltered ) and the test for REM, which would obviously fail since the byte is known to be a space. Nevertheless it may be better programming practice not to take advantage of this fact, and keep the extent of a branch to a minimum, even at the expense of a little unnec­essary processing. This may avoid possible problems later when alter­ing the program. At line 50 we take an unconditional jump - JMP EXAM -back to the start of this bit of code. This instruction takes three bytes and is thus longer than a branch, which has a single byte address field specifying the position of the destination address by giving its offset relative to first byte of the next instruction. If we were de­sperate to save a byte we could, I suppose, write BPL EXAM, relying on the fact that variable I should always be positive after the increment step. But if there happened to be a rogue line of BASIC, that over-ran the maximum of 80 bytes, variable I could go over 127, and set the high order bit, which the 6502 takes as a sign bit. The BPL would fail and NULL would be entered, leading to some extremely hard-to-diagnose sympt­oms. Like a good mountaineer, the programmer should recognise when he is 'exposed' and proceed with caution. Saving a byte by this means here would be like climbing Everest with a clothes line.

NULL puts the zero in the new line as an end marker, and retrieves the pointer to the next old line from where it was preserved by SHOV: it goes in OPLO/OPHI. In 58, we calculate a temporary pointer NJLO/NJHI by adding 1 ( neat use of SEC ! ) to NPLO and J, giving NJLO. NJHI comes from NPHI plus zero - necessary to pick up any carry arising out of the calculation of NJLO. Next we put these temporary bytes into the link in place of those just retrieved for OPLO and OPHI, and finally, now that the pointer NJLO and NJHI has done its job of controlling references to the current new line in the subroutine PUTNPJ invoked at lines 68 and 71, we can transfer into ~t the temporary pointer NJLO and NPHI. This sets it up to refer to the next new line. Now it is necessary to see if it is not only the end of the text line, but the end of the text itself.

In ENDTST we do this. We examine the start of the next line of the original text and see if there are zeros in the link bytes. If either are not zero, a branch to MOV4 takes place. If both are, at line 83 ••• we place similar zeros after the last new line, and at 86 we adjust the two bytes 124,125 we used as the pointers NPLO,NPHI, so they can fulfill their proper role as a pointer to the start of variables. The adjust­ment required is simply the addition of 2. Note that the double length precision addition is carried out on the high order byte by an increment applied if and only if a carry occurred during the low order addition.

The last two sections are the two subroutines for getting and putting the accumulator from and into memory, as specified by the line pointers OPLO/OPHI and NPLO/NPHI and the byte pointers I and J. The .END in line

56

Page 62: The Alien, Numbereater and other Programs for Personal Computers

9 REM----------LOADER-------------10 REM 17 REM-----LOAD M/C CODE TO BUFFER 18 REM 19 I=826:FOR D=1 TO 16;REM 16 DATA'S 20 READA$:PRINT;PRINT:PRINT D,A$; PRINT 30 FOR B=1 TO LEN(A$)/2;REM PER BYTE 40 B$=MID$(A$,B+B-1,2) ;REM ONE BYTE 50 H$=LEFT$(B$,1): GOSUB 200 60 V=W:H$=RIGHT$(B$,1): GOSUB 200 70 V=16*V+W:PRINT I,B$,V:POKE I,V:I=I+1 80 NEXT B,D: PRINT 82 PRINT"LOAD REM-FULL PROGRAM, KEY SYS 826 TO CONDENSE IT, THEN RUN 85 STOP 197 REM 198 REM------HEX TO DECIMAL CONV 199 REM 200 W=ASC(H$)-48 210 IF W>9 THEN W=W-7 220 RETURN 1999 REM 2000 REM-----CASSETTE 2 TO DATA STMTS 2001 REM 2005 V$="0123456789ABCDEF" 2010 FOR I=O TO 15:REM 16 DATA STMTS 2020 PRINT 3000+10*I~ "DATA"~ 2030 FOR J=1 TO 12:REM 12 HEX PER STMT 2040 K=825 + J + 12*I: REM ADDRESS 2050 L=PEEK(K) :L1=INT(L/16) 2060 PRINT MID$(V$,L1+1,1)~ 2070 PRINT MID$(V$,L-16*L1 +1,1)~ 2080 NEXT J:PRINT:NEXT I 2090 PRINT:PRINT"PRESS RETURNS OVER ABOVE DATA STMTS TO INCLUDE IN PROGRAM, 2095 PRINT"THEN SAVE. 2997 REM 2998 REM------- 6502 MACHINE CODE 2999 REM 3000 DATAA57A85E5857CA57B85E6857D 3010 DATA4CC603E6E920EC03F045DOF7 3020 DATA20F103E6E9E6EA20EC03F037 3030 DATAC922F025DOEEA90085E985EA 3040 DATA20EC0320F103E6E9E6EAA5E9 3050 DATAC904DOF020EC03F016C922FO 3060 DATACFC920F00920F103E6EAC98F 3070 DATAFOB9E6E94C7A0320F103A000 3080 DATAB17C85ESC8B17C85E638A57C 3090 DATA65EA85E3A57D690085E4A900 3100 DATA85EAA5E320F103E6EAA5E420 3110 DATAF103857DA5E3857CA90085E9 3120 DATA85EA20EC03D093E6E920EC03 3130 DATAD08C20F103E6EA20F10318A5 3140 DATA7C6902857C9002E67D60A4E9 3150 DATAB1E560A4EA917C6020202020

READY.

Figure 42. LOADER, with REMOVER in DATA

statements

57

Page 63: The Alien, Numbereater and other Programs for Personal Computers

999 tells the Assembler we have finished. You can read how the Assembl­er and Editor work elsewhere. The version I used left the machine code in the second cassette buffer ready for use: however, it is advisable to record it more permanently, and a way to do this is shown in Figure 42, the program LOADER. After loading LOADER, we RUN 2000 which causes 16 DATA statements to be composed on the screen, each of 12 pairs of hex characters representing 192 bytes. K is the memory address, starting at 826 ( corresponding to $033A in the Assembler program ) • L gets the value of the byte at K, and L is decomposed into Ll, the value of the lefthand hex character, and the remainder, L - 16*Ll. These values are used to pull the corr­esponding character from the string V$ and the result printed on the screen. At the end, the new data statements are incorporated into the program by the procedure specified in line 2090.

If we want to restore the program we do so by loading LOADER again, and pressing RUN, which takes the values from the DATA statements and loads them into the cassette buffer. A different approach is used to convert between hex and decimal in the subroutine at 200, which relies on the fact that the characters 0 - 9 have ASCII codes 48 more than their face value, and A, treated as 10, has a code ( 65 ) 55 more than its value.

We have just used English as a method of describing a program: do you think it was successful? Its main advantage is its familiarity, and the way one can try various approaches to getting one's point across -analogy, repetition in different words etc. Its drawback is its lack of definition, in that words like 'if' may have a weaker meaning in everyday speech than their meaning in this special context ( 'if and only if' ) • Moreover, a report in English whether spoken or written is linear - it can say 'A is related to B and also to C' but has to wait for a later sentence to find out more about B and C: a picture, on the other hand, can be scanned in two dimensions and shows A, B, and C simultaneously:

A <----- B

v c

we see quickly that B and C are not directly related. However, the dia­gram cannot, as words can, show what these arrows mean: their meaning must be implicit in the formalism of the chart concerned, or defined in words as a footnote.

~et us a look at a formal way of describing REMOVER - a Decision Table:

This table shows the rules according to which certain actions are taken in the program. Various conditions can occur, sometimes excluding one another, sometimes independently - for example, clearly Byte=space can­not co-exist with Byte=quote, but can co-exist with REM mode. A dash indicates 'don't care' i.e. in this combination of conditions ( a rule this particular condition may be true or false without affecting the actions the rule demands.

In the Actions block ticks are usually employed to show which actions are to be done under which rules, but I have used + since although my PET has quite a nice tick symbol, ( shift and colon in lower case mode my printer does not.

58

Page 64: The Alien, Numbereater and other Programs for Personal Computers

Conditions

Start mode End of line End of text REM mode Quote mode Byte= space Byte=quote Byte=REl~

Actions

Setup,SM off Get a byte Put a byte Save links Quote mode on Quote mode off REM mode on REM mode off Reenter table Wrapup & stop

Rules

y NN N N N N N N N - y N N N N N N N N - - y N N N N N N N

- Y N N N N N N - N y y N N N N - - N - y N N N

- - - - y N N y N N - - N - N N y N

Figure 43. Decision Table.

+ + + + + + + + + +

+ + + + + + +

+ + + +

+ + + + + + + + + + + +

+

The Decision Table is a useful device for checking that every combinat­ion of conditions has been considered, particularly when they are indep­endent and the Condition block looks like

a Y Y Y Y N N N N b Y Y N N Y Y N N c Y N Y N Y N Y N

but it can hardly be said to show the overall function of the program -if anything, it is less clear than even the program itself.

Consider next the classic 'block diagram' (Figure 44 ). Here the lines represent the sequential flow of control within the program. The tempt­ation to elaborate the chart is almost irresistible, and when the number of rectangles and diamonds and lines gets out of hand, the diagram spreads over several sheets, with connectors from one to another. The way out of this mess is to get the whole block diagram on one A4 sheet, with blocks representing large chunks of complex logic to be elaborated on subsidiary sheets.

The truth is that a logic block diagram simply models the program and helps you see possible sequences of operation - in particular, while a program shows only 'GO TO' links explicitly, the diagram shows 'where from', which helps when making changes. But it does not help us very much to appreciate the overall objective of the program, or prove that it will achieve it, or even ( as at least the decision table does ) show that certain cases have been overlooked.

59

Page 65: The Alien, Numbereater and other Programs for Personal Computers

end of line

NULL get link to next line

QUOTE

end of line

copy byte yes and get another

REM copy byte and get another

other

other

Set up Pointers

end of BASIC text

not end yet

MOV and SHOV copy link and statement nunt>er

SAVE copy byte

no

write end of text markers and new start of variables

Figure 44. REMOVER Block Diagram

60

Page 66: The Alien, Numbereater and other Programs for Personal Computers

Prime? (by Tom Race )

A few months ago I was set the following problem as a maths homework:

'Find the smallest set of twenty consecutive numbers, none of which is a prime.'

This was obviously a job for a computer! I immediately wrote a crude version of the program 'PRIME?', see Figure 45.

In line 20 the operator INPUTs 'n' i.e. 20, which is also named N in the program, for convenience. T is incremented in line 30, this is the number we are testing. The PRINT statement in this line is a check, for me to see how far the program has reached. This eliminates having to STOP, PRINT T, and CONT.,which an impatient schoolboy, such as I, would inevitably do.

Line 40 sends us off into a GOSUBroutine, testing T for 'primeness'. Line 90 initiates Y as a divisor into T, which carries on testing for an INTeger value of T/Y, meaning that Tis not a prime and is therefore 'OK'. (I prefer this sign, rather than setting some strange variable to one, or something like that.) The testing continues until either T/Y is an INTeger,in which case we RETURN to line SO, or Y grows to the SQRoot of T, and further testing is obviously pointless,in which case X$ is eliminated, and we RETURN anyway.

This subroutine can be used on its own with a few alterations, such as an INPUT command, and a PRINT statement or two, as an ordinary prime number tester, as I have proved on other preps involving primes!

Back in lines 50 and 60 we either add to, or reset D, the 'distance' from the last prime, depending on whether T is 'OK' or not. Line 65 tests to see whether we now have a string of non-primes longer than the previous record, if so L is adjusted accordingly. Next comes the most inportant test: have we finished? If so we PRINT the results, set all variables to 0, and get another problem. Otherwise we GOTO 30, and try again.

FOOTNOTE: This program correctly gave the answer 1130-1149, (NOT 1130-1150, as a

moment's thought will show.) My maths master tried to solve this problem by 'factors, and came up with some eight-digit number!!

1 REM ------PRIME?-----5 REM 10 PRINT"[CLR] *****TO FIND 1ST N NON-PRIMES**** 20 INPUT "[OWN] PLEASE INPUT 'N'";N:PRINT:PRINT 30 T=T+l:PRINT"[UP]"T" LONGEST RON ";L;" THIS ROW ";D" " 40 GOSUB 90:REM---TEST TRY 50 IF X$="0K"THEN D=D+l 60 IF X$<>"OK"THEN D=O 65 IF D>L THEN L=D 70 IF D=N THEN 130 80 GOTO 30 90 FOR Y=2 TO INT(SQR(T)) 100 A=T/Y 110 IF A=INT (A) THEN X$="0K": RETU!U~ 120 NEXT:X$="":RETURN 130 PRINT "[OWN] THE FIRST";N;"CONSECUTIVE NON-PRIMES ARE "T-N+1"-"T 140 T=O:N=O:L=O:GOTO 20

READY.

Figure 45. PRIME? program

61

Page 67: The Alien, Numbereater and other Programs for Personal Computers

Playfair Techniques: substitution cypher, repeatable random sequences, error messages, pseudo-cursor, POS.

In a simple cypher, each letter in the 'plaintext' is altered to another so that, for example, DOG may become CAT and GOD will become TAC. Such cyphers are easy to break if there is enough material, by trying to find frequently occurring letters such as E T A 0 N I R s H and common words like THE and AND.

Recently far more sophisticated cyphers based on prime number theory and making use of computers have been invented, and are, as far as we know, unbreakable for all practical purposes. However, it is possible to use substitution cyphers which are more reliable than the simple variety des­cribed first, though not as advanced as the latest 'trapdoor' or 'knap­sack' cryptographic systems.

One of these is 'Playfair', in which the plaintext is broken grams each of two different letters: spaces are ignored and Next we form a key consisting of a 5 x 5 square of letters. out of 26 has to be sacrificed -usually J, for which I does

up into bi­Js made Is. One letter duty.

This key is used to encypher each plaintext bigram by these rules:

If the two letters of the bigram are on the same row, the new one consists of the two letters each of which is one to the right of the corresponding one in the original. If either is at the extreme right of the row, it is encyphered to the one on the extreme left by 'wrap-round'.

If the two original letters in a plaintext bigram are in the same column, the new are those below the old, with wrap-round as above.

Otherwise the two letters are diagonally opposite. The new bigram will consist of the letters on the ends of the other diagonal of the rectangle defined by the first.

See Figure 46 for an encyphering pass of PLAYFAIR. The run begins by building the 5 x 5 key by randomly taking letters out of the alphabet and putting them into successive squares in the key, which is shown.

After some explanations, the experimenter is invited to enter his text: as he does so, anything but A-Z ( e.g. spaces ) are ignored and Xs put in to avoid repeated letters and to make an exact number of bigrams. We use GET not INPUT, processing character by character, not waiting for a RETURN to be pressed.

The program can be used to cypher or decypher. The experimenter chooses to encypher and his message is processed in bigram form, then displayed ( RESULT ) as a text-string without spaces.

Figure 47. shows the reverse process and Figure 48. gives an explanation of the row and rectangle rules described above.

Now examine the program, Figure 49a,b• Note that DIM A$(4,4) defines an array with 5 x 5 elements since 0 is a valid subscript. Using a negat­ive argument for RND sets it to a reproduceable series, useful for test­ing and perhaps for practical use of this program: the initial seed of the random number generator would in effect be the encryption key.

62

Page 68: The Alien, Numbereater and other Programs for Personal Computers

RUN +ABCDEFGHIKLMNOPQRSTUVWXYZ+ +ABCDEFGHIKLMNOPQRSUVWXYZ+ +ABCDEFGHKLMNOPQRSUVWXYZ+ +ABCDEFGHKLMNOPQRS~RXYZ+

+ABCEFGHKLMNOPQRSVWXYZ+ +ABCEFGHKLMNOPRSVWXYZ+ +ABCEFGHKLMNOPRSVXYZ+ +ABCEFGHKLMNOPRSVXY+ +BCEFGHKLMNOPRSVXY+ +CEFGHKLMNOPRSVXY+ +CEFGHKLMNPRSVXY+ +CEFGKLMNPRSVXY+ +CEFGKMNPRSV~{Y + +CEFGKMNPRSXY+ +EFGKMNPRSXY+ +EFGMNPRSXY+ +EFMNPRSXY+ +EFMNPSXY+ +EFMNPSY+ +FMNPSY+ +MPSY+ +MPY+ +PY+ +Y+

!T!N!H!G!F! !-!-!-!-!-! !I!Z!L!R!S! !-!-!-!-!-! !U!A!V!X!M! !-!-!-!-!-! !D!B!C!E!P! !-!-!-!-!-! !Q!O!K!N!Y!

SPACES AND ANY BUT A-Z WILL BE IGNORED. 'J'S WILL BE CONVERTED TO 'I'. MESSAGE CAN BE UP TO 30 LETTERS LONG. DOUBLE LETTERS HAVE 'X' PUT IN BETWEEN THEM. TEXT?THEQUICKBROWNFOXIUMPSOVERTHELA

CYPHER/DECYPHER C/D? C TH EQ UI CK BR OW NF OX IU MP SO VE WG ON DU KH EZ WZ YG NA UD PY ZY XC

RT HE LA IG GC ZV

RESULT: WGDNDUKHEZWZYGNAUDPYZYXCIGGCZV

T w H G F

J z L R s u A v X M

D B c E p

() 0 K N "'

It looks better with PET graphics!)

Figure 46. Encyphering using PLAYFAIR

63

Page 69: The Alien, Numbereater and other Programs for Personal Computers

HIT 'S' FOR SAME 'N' FOR NEW KEY ( S vvas pressed }

SPACES AND ANY BUT A-Z WILL BE IGNORED. 'J'S WILL BE CONVERTED TO 'I'. MESSAGE CAN BE UP TO 30 LETTERS LONG. DOUBLE LETTERS HAVE 'X' PUT IN BETWEEN THEM.

TEXT?WGDNDUKHEZWZYGNAUDPYZYXCIGGCZV

CYPHER/DECYPHER C/D? D WG DN DU KH EZ WZ YG NA UD PY ZY XC TH EQ UI CK BR OW NF OX IU MP SO VE

IG GC ZV RT HE LA

RESULT: THEQUICKBROWNFOXIUMPSOVERTHELA

!T!W!H!G! !

!D! ! !E! ! !-!-!-!-!-! !Q! ! !N! !

Figure 47. Decyphering using PLAYFAIR

T->W H->G

E->D Q->N

Figure 48. Substitution: where both letters

of bigram are on same or different rows

64

Page 70: The Alien, Numbereater and other Programs for Personal Computers

0 REM---------PLAYFAIR--------------1 REM 10 DIM A$ (4,4): 15 R=RND(-1): 20 C$="+ABCDEFGHIKLMNOPQRSTUVWXYZ+": 21 FOR I=O TO 4: FOR J=O TO 4: 22 PRINT C$ 25 R=INT((LEN(C$)-2)*RND(5))+2: 30 B$=MID$(C$,R,1) 45 C$=LEFT$(C$,R-1) + RIGHT$(C$,LEN(C$)-R): 50 A$(I,J)=B$: NEXT J,I 55 REM 56 REM-------DISPLAY CREATED SQUARE 57 REM 60 PRINT" --------- " 70 FOR J=O TO 4 80 PRINT"!";: FOR I=O TO 4 90 PRINTA$(I,J); "!";:NEXT I 100 PRINT:IF J<4 THEN PRINT"!-!-!-!-!-! 120 NEXT ,T 130 PRINT" --------- " 197 REM 198 REM-------LET EXPERIMENTER ENTER TEXT 199 "REM 200 GOSUB 700 205 INPUT "CYPHER/DECYPHER C/D";Z$ 207 AD=+1:IF Z$="D" T'HEN AD=+4: GOTO 210

REM PLAYFAIR SQUARE OF 5 BY 5 REM FORCE SAME RANDOM NO TO START REM NB USE J FOR I REM BUILD UP PLAYFAIR SQUARE

REM RANDOM CHOICE IN RANGE 2 TO 26

REM CUT B$ OUT OF C$

208 IF Z$<>"C" THEN PRINT"1MUST BE CORD ! ! 1":FOR I=1T02000:NEXT:GOTO 205 210 U$="":FOR K=1 TO LEN(T$) STEP 2 220 P$(0)=MID$(T$,K,1): P$(1)=MID$(T$,K+l,1) 230 P=O: GOSUB 600: P=1: GOSUB 600 240 IF X(O)=X(1)THEN 300: 250 IF Y(O)=Y(1) THEN 400: 260 Q$(0)=A$(X(1),Y(O)): 270 Q$(1)=A$(X(O),Y(1)) 280 G0TO 500 300 IF Y(O)<>Y(1) THEN 320

REr-1 SAME COL REM SA.MJ<.: ROW REM RECTANGULAR SUBSTITUTION

310 PRINT:PRINT"ERROR,REPEl\.TED LETTER" :STOP: REM INSERT-X SHD PREVENT TBIS 320 NY=Y(O)+AD: IF NY>4 THEN NY=NY-5: REM WRAP ROUND COLUMN 330 Q$(0)=A$ (X(O) ,NY) 340 NY=Y(l)+AD: IF NY>4 THEN NY=NY-5 350 Q$(1)=A$(X(1) ,NY): GOTO 500: 400 NX=X(O)+AD: IF NX>4 THEN NX=NX-5 410 Q$(0)=A$(NX,Y(O)) 420 NX=X(1)+AD: IF NX>4 THEN NX=NX-5 430 Q$(1)=A$(NX,Y(O)) :GOTO 500

REM SUBSTITUTE

500 PRINT" ";P$(0);P$(1);"==";Q$(0);Q$(1);"1";:IF POS(0)>35 THEN PRINT"" 510 U$=U$+Q$(0)+Q$(1) :NEXT K 515 PRINT:PRINT"RESULT: ";U$ 520 PRINT:PRINT"HIT 'S' FOR SAME, 'N' FOR NEW KEY" 530 GET Z$:IF Z$="" THEN 530 540 IF Z$="N" THEN 20 550 IF Z$="S" THEN 200 555 GOTO 530 597 REM 598 REM---------FIND ROW, COL 600 REM 605 FOR I=O TO 4: FOR J=O TO 4 610 IF A$(I,J)=P$(P)THEN 630 620 NEXT J,I:PRINT:PRINT"ERROR, QUEER LETTER OR ODD NUMBER":STOP 630 X(P)=I:Y(P)=J 640 RETURN

Figure 49a. PLAYFAIR program, first part

65

Page 71: The Alien, Numbereater and other Programs for Personal Computers

Lines 21 to 50 build up the key. Note how 'sampling without replace­ment' is achieved by resetting C$ to the two pieces on either side of, but omitting, the letter removed. 56-130 shows the completed square. The original program version used PET graphics rather than ! and - signs which gave a good effect on the screen, see Figure 46, but did not suit the printer. At 205 the program sets a variable AD which will control whether the encyphered letter is taken as the one above or below or left or right of the plain-text letter. In 208 the '1' in the PRINT is act­ually a 'cursor up' followed by 'reverse field' so the error message overwrites the original prompt, in black-on-white, then, after a count of 2000 to give the experimenter time to see it, the original prompt is re­displayed. U$ will hold the results. At 210 we examine the input, bi­gram by bigram, each represented by the pair P$(0), P$(1). There is no need to DIMension PS because it has less than 10 elements.

Subroutine 600 searches for the letter P$() in the square and returns its coordinates X() andY(). 240-260 determine which rule is to apply.

260/270 obtain the encyphered bigram in QS() for the rectangular case, which, one can see, does not use AD because cypher/decypher use exactly the same method. 300-350 deal with a bigram with both letters in the same column, 400-430 in the same row. Check how AD inverts the order of letter extraction.

At 500 all methods converge: the bigrams old and new are printed - the PET is made to print the lower bigram by two 'back cursors' before the printing of the encyphered bigram, followed by an 'up cursor' to get back onto the plaintext line. POS gives us the cursor position and if we are about to reach the edge, warns us to get a new bigram line started. us is built up from the bigrams and finally when all have been processed, at line 515, the results are shown and the experimenter given the chance to stick with the current key ( to encypher more text, or decypher what he has just encyphered ) or try a new one.

Figure 49b gives details of the technique for GETting input and check­ing it for range ( 723 ) etc. 720 looks for carriage RETURN. Note that a flashing cursor is provided by printing 'space and back-cursor' before and 'reverse-field, space, reverse-off, back-cursor' after the test for no key-depression. Line 730 checks and inserts an 'X' if both letters in the bigram are the same. Note: we count bigrams by adding .5 into L on each letter so that we can check whether the current letter is still part of the old, or the start of a new bigram, by seeing if L is integer.

Try PLAYFAIR, for commercial or erotic messages!

697 REM 698 REM--------BUILD STRING 699 REM 700 PRINT "SPACES AND ANY BUT A-Z WILL BE 7 0 2 PRINT II I J I s WILL BE CONVERTED TO I I I •

703 PRINT "CAN BE UP TO 30 LETTERS LONG. 704 PRINT "LETTERS HAVE 'X' PUT IN BETWEEN 705 L=O:T$=" 11 :CS="":PRINT "TEXT?"~

IGNORED. II

MESSAGE DOUBLE

THE..M. II

710 GET BS:PRINT" = 11 piF BS= 1111 THEN PRINT" 2= 11 ~:GOTO 710 720 A=ASC(BSl:IF A= 13 THEN 760 723 IF B$<"A11 OR B$>"Z" THEN 710 725 IF B$="J" THEN B$="I" 730 IF (B$=C$) AND (L>INT(L)) THEN TS=TS+"X":PRINT 11X"~:L=L+.S 740 PRINT BS~:C$=BS:TS=TS+BS: L=L+.S 750 IF L<lS THEN 710 760 IF INT(L)<L THEN TS=TS+"X11 :

770 PRINT:PRINT 780 RETURN

READY.

REM ENSURE BIGRAMS

Figure 49b. PLAYFAIR program, second part

Page 72: The Alien, Numbereater and other Programs for Personal Computers

The Alien Techniques: 3D moving graphics, animation, control of electric light.

The Alien was written to frighten my wife and children, Christmas 1979. The XXth Century Fox film of the same name had just been released: as a matter of fact I had been a technical consultant on the presentation of the spacecraft instruments, having a strong interest in the field of aviation and simulation.

You can frighten your family too with this program, but for best results connect an electric light to your PET via a solid state relay such as Radio Spares 349-692, driven by any of the 8 user port lines. Arrange for the room to be in dimness except for a light connected in this way, and when the program finally presents the dread monster, the room will be plunged into darkness except for the gnashing jaws and flashing eyes on the VDU. They do really scream!

The results are shown in Figure 50. The program appears as Figure 51. Line 1 deals with the registers within the VIA ( Versatile Interface Adapter ) which are addressed like memory. 59459 determines whether the 8 output 'user port' lines are input or output: 255 is binary 11111111 which sets them all to output. 59471 is the set of values they are to take - all zero. Since I route the output of the user port through a home-made box containing some inverting integrated circuits ( to avoid possibly blowing up the PET ) , these zeros will result in 5 volts going to the relay, and so putting on the light.

**** NOTE: MAINS VOLTAGES ARE DANGEROUS AND THE WIRING OF THE RELAY MUST BE DONE BY QUALIFIED PEOPLE OR DAMAGE TO YOU AND YOUR COMPUTER IS LIKELY *******

In line 2 all those GET A$'s are to clear the input buffer in case a trembling finger put a few characters in it. In 6, I initialised a con­stant PI: the PET has an excellent little pi sign, but the printer did not like it in line 110. 3 - 96 draws the corridor of evil: I printed it in lowercase mode to help you key the program in. The 3 in line 8 is a clear-screen.

At line 100 we prepare to show a little object hopping towards us. B is its last position - if we do not arrange to blank it, it will leave a trail of selves behind it. One is bad enough. Now we come to the part which computes the position of the approaching Alien. We want to work out the memory location relative to the screen origin O, where the ob­server will see the horror. Imagine we are looking down the corridor through a window and mark on the window where the line of sight from our eye to the monster goes through. Technically we are 'mapping a 3D scene onto a 2D surface'. If rotation of the scene etc was possible we could use various sophisticated algorithms, but this is a simpler case.

The observer's eye is, say, 5' above the ground and the Alien is init­ially 70' away. (My apologies for not working in metric). If the Alien is at floor level the observer's line of sight is initially depr­essed down from the horizontal by ARCTAN (5/70) • The Alien moves from 70' away to 5' away. The old position is saved in Bl and the new one found by taking the ARCTAN and converting this from radians to degrees and rescaling for the screen, with an offset of 7 from the top of the screen. Lines 110-113 select the size of Alien to show - it gets bigger as it approaches. Lines 115-120 erase the old Alien and put in the new.

67

Page 73: The Alien, Numbereater and other Programs for Personal Computers

ARE YOU READY FOR ••••

THE A L I E N ? ? ? 1

PRESS ANY KEY IF S1). Y•)U FN'•L

\.

.. j -. ~

,~, ,-., -- --

••••• ................

Figure SO. Results of ALIEN

68

')

Page 74: The Alien, Numbereater and other Programs for Personal Computers

0 REM-------- ALIEN ---------1 POKE59459,255:POKE59471,0: 2 PRINT"3ARE YOU READY FOR •••• ": 3 PRINT"THE A L I E N ? ? ? ! ! 4 PRINT"PRESS ANY KEY IF SO, YOU FOOL 5 GETA$:IF A$="" THEN 5: 6 0=32767:POKE 59468,12:PI=3.1415926: 8 PRINT" 3 rn n 10 PRINT" rn n 2 0 PRINT" rn n 3 0 PRINT" rn n 40 PRINT" o#i####p 50 PRINT" nrn % n##rn ' 60 PRINT" %' %' %' 7 0 PRINT" % I % I % I 80 PRINT" %I %I %I 9 0 PRINT" % I 1 : 1 : 91 PRINT" %' n 92 PRINT" l:n 93 PRINT" % 94 PRINT" % 95 PRINT" n 96 PRINT" n 97 PRINT"

rn rn

rn rn

rn rn

100 B=O: FOR F=70 TO 5 STEP -1: 110 Bl=B: B=INT(ATN(5/F)*45/PI)+7: 112 IF F<l5 THEN A=l60 113 IF(F<25)AND(F>l5)THEN A=l08: 115 POKE 0+40*Bl+l2,32: 120 POKE 0+40*B+l2 ,A: 130 NEXT:POKE 59471,255: 132 PRINT"3 133 PRINT" $$$$$$$$$$$$ 135 PRINT" n rn 14 0 PRINT" n u' ' 'i u' ' 'i rn 150 PRINT" % 160 PRINT" % 170 PRINT" 'rn% 180 PRINT" I % 190 PRINT" rn) 200 PRINT" (

) I

) ) I

j'''k j' ''k 'n% 00 I %

)n (

REM SET USER PORT TO OUT, NO SIGNAL A=ASC(".") :GETA$:GETA$:GETA$:GETA$

REM WAIT FOR START REM VDU MEMORY ORIGIN, SET GRAPHICS

REM IT 1 S COMING TOWARDS US! ! REM CALCULATE SCREEN POSITION

REM ALIEN'S APPARENT SIZE REM BLANK OLD POSITION REM PUT ALIEN IN NEW REM LIGHTS OUT ! !

210 PRINT" ) ) ) ) ) % G G R R R R 220 PliNT" rn ))))2 n 230 PRINT" rn n 240 PRINT" ########## 111111111" 260 FOR I=l TO 20: FOR J=l TO 200: NEXT 270 PRINT"q q 1 ": REM FLASH EYES 280 FORJ=lT0200:NEXT 290 PRINT" q ql 300 NEXT I:GOTO 0

READY.

Figure 51. ALIEN program

69

Page 75: The Alien, Numbereater and other Programs for Personal Computers

Line 130 sends 5 volts on every line of the user port ( an overkill, but it saves sorting out which is which ) , to turn off the light. 132-240 show the horrible face and 260-270 flash the eyes from side to side.

After a spell of this, the program returns to line 0 to frighten the next relative or friend. It is strongly recommended for Christmas or Hallowe'en.

My son says this program is rather silly, but I believe it is because he secretly finds it truly terrifying.

8 PR INT"IW 18 PRINT 28 PRINT" 38 PRINT" 48 PRINT" 58 PRINT" 68 PRINT" 78 PRINT" 88 PRINT" 98 PRINT" 91 PRINT" 92 PRINT" 93 PRINT"

A Note on Photographing the VDU.

I

In many cases a cheap and effective way of getting hard copy from a home computer is to photograph the screen. This is particularly useful when the special graphics characters do not come out on a printer, and when the volume of printed output is too low to justify the purchase of one anyway.

Use a slow, fine grain film and a long exposure to avoid catching the screen between frames, which can give a barred effect. Invert the image by running a little program to change all characters from white on black to black on white and vice-versa, after blanking out the line in which the program was invoked by, e.g., RUN 10000

10000 PRINT 'up-cursor, blanks to get rid of RUN 10000' 10010 FOR I=32768 TO 33767 10020 X=PEEK(I) + 128 10030 IF X>255 THEN X=X-256 10040 POKE I,X: NEXT 10050 GOTO 10050: REM TO STOP 'READY.'

Use a tripod and a cable release, and take the picture in the dark if possible to prevent screen reflections. Aim to get the white parts of the picture completely saturated, so a meter reading should suggest overexpos­ure, if anything.

70

Page 76: The Alien, Numbereater and other Programs for Personal Computers

CJenerallnputPrograrn This book was written using the cheapest of equipment and the simplest of software. GIP is used to enter text at the original small PET key­board and store it in program form. In this way one can use the very effective PET program editor to correct errors. Then the 'program' - in fact an unexecutable chunk of text - can be listed on a printer with LIST, or better, to avoid the line numbers coming out, by a small pro­gram which traverses the text in PET memory using the information given in REMOVER. See Figure 52 for the program - photographed, to show the special PET graphics symbols.

GIP asks the writer for the interval between line numbers: 10 is a good value, and the first to use, e.g. 1000. It then fills the screen with line numbers and inverted commas ready to be filled with text, and gives a prompt on what has to be done at the bottom of the screen: see Figure 54 • It gives the writer a little blinking asterisk ( so one remembers one is in GIP not BASIC ) as cursor, and this little cursor is constr­ained to avoid overwriting the line numbers etc. Now you enter the text using RETURN, cursor forward/back as required ( although the program does not allow many of the nice features of the PET editor, the main idea at this stage is to bang in the text at maximum speed and correct afterwards- the philosophy of word-processing).

The writer now finds upper and lower case transposed from the usual PET mode to the natural typewriter style. At the end of the page he presses STOP and RETURNs over the lines just written to incorporate them in the program. Then they can be edited and listed or indeed SAVEd.

Some notes on the program follow. See also Figure 53, a logic block diagram.

Line 2 sets lower-case graphics and says how much memory is left. It asks for the interval, giving a suggested value 01, so pressing RETURN sets 01 by default. Similarly line 5 reminds us that 63999 is the high­est line number. The screen is cleared and the length of the line num­ber, +2, recorded for controlling the remaining line length. Lines 10-12 fill the screen with line numbers and opening quote signs.

In line 15, L is the value of the bit in a byte controlling upper/lower case: we shall need this for dissecting the incoming characters. I is the current character position in the line. At 20 we move the cursor to the position immediately after the quote, and in 30, GET key-strokes. Note that we use ATN as a way of introducing a delay for the cursor's flash, from 'asterisk, back-cursor' to 'reverse-field, asterisk, reverse­off'.

At 35 the new character A$ is examined. Characters ASCII 19, 147 etc are: cursor home, clear screen, cursor down, delete, insert, double­quote. We do not want these to intervene. Line 37 tests for cursor-up which is used to enable one to get back to the start of the current line unless ( line 38 ) one is already there. At 40 the character is dis­sected further to invert upper and lower case: U is the original byte's high order bit. The test IF (K-U)>64) AND (K-U<91) checks that the byte is in the alphabet, and if so, K=L+K-U-U has the effect of flipping the high bit since if K had a high bit then U=l28, and K=l28+K-128-128 takes

71

Page 77: The Alien, Numbereater and other Programs for Personal Computers

K's high bit off, but if U=O, then K=l28+K-0-0 endows K with a high bit it did not have before.

Line 41 tests for RETURN and if so 41 finishes the current line with spaces. Line 45 makes a cursor-forward the same as a space. Line 50 presents the character which has survived all the torture just described on the VDU and checks for cursor-back: if it is, I is back-spaced two, and either way I is advanced one, so if K was a cursor-back there is a net one back. At 60 I is tested to see if a new line is needed, and if it is not, a check is made to see if back-spacing may have got I into the line number/quote mark area defined by Ll: if so, it is backed all the way back to the end of the next line, in 66.

GIP is probably the program I use most, together with programs not des­cribed in this book - KWOC, a program for Keyword Out of Context analysis useful for sorting indices like the one at the end of this book, and VAT, a program for calculating and checking VAT amounts. The three together form the nucleus of my personal accounting system.

~ REM--------- GIP --------JP~P~CE 1?~2

2 POKE59468 14 PR INT"FREE II • FPE I 0 I lllF'I_­"interval ail ... " ,D

S INPUT "1st ""Tlne 639991111.11" .L PRINT"EI" :Li=LEN<STRS<U)+""T- _ _

18 FORI=1T018:PRINT"IJ"·L "11".1..HPf~=:4~: PRINT"III" :L=L+D:NEXT.PRiNT"R. S.) .P;I':

12 PRINT"'KEY· OPEN1,1,1/CMD1.·LbT1~tB:•-/CLOSE11!1"

15 L=f28 · I=L1+1 28 PRINT SPC (L1> 38 GETA$·PRINT"da", X=ftmiLI PPlllT"IIII

: I FA$ ="" THEN~8 3$ K=ASC<A$). IFK=190RK=1470RI'=17rJPI'=20·:·

RK= 1480RK=34 THEN38 37 IFK<>145THEN48 38 IF I<L1+2THEN38 39 FORI=ITOL1+2STEP-1:PRINT "II". llE:-·r

:GOTO 38 48 U=KANDL IF <K-U)64) AND <K-U(91 1 THEW =L

+K-U-U 41 IFK<>13THEN45 42 FORI=IT088:PRINT" ".NEXT ~~or.:~l': 45 IF K=29THENK=32 58 PR INTCHR$<K), IF1<0157THEN613 55 1=1-2 68 1=1+1: IFI>79THENPRINT" II. I~I)T("~l': 65 IF I>L 1 THEN78 66 I =79 PRINT "BIB!" . SPC ( ';"9-Ll I . 78 GOTO 38

Figure 52. GIP General Input Program

88 REM---------------------------------TEXT

98 REM _ see 1 II Text 1 put into the program b 1::.1 ·~IF' 1tself fol owed by RETURNs down the see2 II screen 0 The PET editor can then

be used to correct it, and 1 t can l?e see3 II saved by SAVE or as a data t 1 1 e 1:·

y the sequence : see4 II OPEN 1, 1, 1 sees " CMD 1 see6 II LIST 1888-see7 II CLOSE 1 sees "

72

Page 78: The Alien, Numbereater and other Programs for Personal Computers

z )[1Ut5 trJ !bnd of

ttdfum

21 f1't-l hf-ft{/fF(. I Ht tl'l;twvJi. !mdft.rK une ?W.

S-1Z =./ua of~~

Jlts

15" I Jd Cffl:f~ whfit {Jth~ 1

3o I G-ET tt b!fte. ft.qm k.~boltffi

Bttd< to start of titiS (A 11t.

Figure 53. GIP Logic Block diagram

1uJ

S51

L#U f_()f!VtU es I -:z,

{,I,

i1m:t i l7t(w

tv ~1:1 Prttf$

yu

calculating

Prttf$

Prttf$ Prttf$

Prttf$

Prttf$ Prttf$ Prttf$ Prttf$

Prttf$ Prttf$

Prttf$ Prttf$ Prttf$

Prttf$ Prttf$

Prttf$ Prttf$

Page 79: The Alien, Numbereater and other Programs for Personal Computers

RUN FREE 1407 interval? 05 1st line? 1000

1000 "

1005 "

1010 "

1015 "

1020 "

1025 "

1030 "

1035 "

1040 "

1045 "

R/S, II. ,RETS KEY: OPENl,l,l/CMDl/LISTlOOO-/CLOSEl

1000 "This book was written using the che apest of equipment and the simplest 1005 "of software. GIP is used to enter text at the original small PET key-1010 "board and store it in program form

In this way .•••••••••••.• 1015 "

1020 "

1025 "

1030 "

1035 °

1040 "

1045 "

R/S, A, RETS KEY: OPENl,l,l/CMDl/LISTlOOO-/CLOSEl

SET UP

PET PROVIDES FRAME

AUTHOR FILLS FRA}ffi

Figure 54. Presentation to writer by GIP

74

Page 80: The Alien, Numbereater and other Programs for Personal Computers

Clear This little program is used to clear away sections of a BASIC program by an amusing method. It is not possible in PET BASIC to say

DELETE 200-250

or similar, but individually deleting lines by keying in their numbers is a bore. CLEAR ( see Figure 55 below ) does it a bit better.

At line 71 the screen is cleared and the cursor moved down a couple of lines, and the number 0999 written. In line 72 we fill the keyboard buffer pointer with the value 4 to indicate it has four entries, which we now make in 73: they are all RETURNs. While in this loop we examine four positions on the screen - 32849 to 32849 + 3. These are in fact the digits of the 0999 we wrote earlier. Each ASCI value has 48 removed to bring it into the range 0 - 9 and in J, the whole decimal value is built up, so the first time round J = 999. Now we increment J, which is to be a statement number, and test to see if we have dealt with all up to 9998, the highest CLEAR can manage.

In line 75 we move the cursor back up to the line where we wrote 0999 and print the new statement number and the command, RUN 72. Then - we actually leave the program ! But the BASIC interpreter finds the cursor over a statement number - 1000, initially - and there are some RETURNs in the keyboard buffer just as if you or I had entered some commands we wanted executing. So BASIC deletes line 1000 and then finds RUN 72 - so it re-enters the program again, and this time it will delete 1001. And so on. Figure 56 shows the screen during this process.

There are faster and simpler ways of doing this, but CLEAR is quite a perverse little program, which reminds me of Edgar Allan Poe's hero who used to let himself die a few minutes - hours - and then revive. But in the end he overdid it and was not in very good shape when he came round: 'cause of death, death!'. CLEAR is not quite such a macabre creature, but it seems to like ENDing itself, after ensuring its resurrection.

71 PR lllT"mlt.l ~~1'?'?'?" F'Et·t----- ·:LEr'1F' -----

7 2 POk.E52':. 4 J=B F•:•P I=~:n•:•3 7 3 POk.E526 +I . 13 J= lB+ J +PED • I+ :::2:::4·? • --1: 74 NEXT J=J+l IF J='?'?'?'3THENST•:•P 75 PRINT"ml!l" .J PRltH"RUN7~". EIH•

Figure 55. CLEAR program

F'Er'1I• 't' lt:114

RUN72

Figure 56. CLEAR in operation

75

Page 81: The Alien, Numbereater and other Programs for Personal Computers

·::. ••••• • 1 • 47 ::: ••• 4 .,. I ·:..:.€, •

4 '::. • 0 • .2

t::. " -·

• 2

c- ':·

::: 4 ·? 1

~ . .: . .::.·

.::.: 4.2

~:

'=·

-:::

::: ':<

1

8

Figure 57. Results of NUMBEREATER

76

Page 82: The Alien, Numbereater and other Programs for Personal Computers

~unrrbereater Techniques: two person game.

Numbereater was one of the first PET programs I wrote and is rather clum­sy in some respects, and though the main logic is quite well-structured, it could have been made shorter, quicker, and clearer. Perhaps you could tidy it up ? It does however go down well as a game, probably be­cause it is the only one for two people, and it has an element of both skill and chance. Try it now.

You should see results like Figure 57. Two areas of the keyboard are used as 'joysticks' to move two little 'eaters' around the screen indep­endently: each 'eats' any digits on the screen it meets, and the first player whose eater gets 100 wins. If you run over 100 your score goes back to zero. The nasty player soon realises that he can 'eat' his own and his opponent's score as well as the digits provided. The delays in the program also cause some maddening but quite anmsing effects.

Now consider the program in Figures 58 and 59. It starts well, with a classic structured layout in 10 - 99. D$ should be explained, but it is easy to see it is a flag set when someone has won a game. The subrout­ines are rather haphazardly numbered, so the first we meet is GET A CHAR which elsewhere I have written 'in line' since GOSUB 200 is slower and takes up nearly as much room as GET A$:IFA$=''THENxxx.

MOVE AND EAT is a very ponderous way of interrogating the keys which have been pressed. A better way is described in SUPERLIFE. It is how­ever very clear what is happening, or is it ? The two keyboard areas are interrogated on alternate lines to be fair to both players, but you can see that a large number of unnecessary tests are done. Why test A$ = 'Z' when you have just found A$ = '7' ? An obvious improvement would be to put :GOTO 450 in lines 410 to 430 and :GOTO 477 in lines 450 to 470. Yet oddly enough the true purpose of the game - to entertain - may not be realised quite as well if the processing is faster. It is worth investigating.

Line 477 skips redundant processing if the input is nonsense. 480 to 486 keep the eaters in bounds, and 488 uses subroutines to compute the screen location of the eater which has just moved, its meal, if any, and revisions to its score. 490 to 497 do the same for the other eater. I am sure that if I had been more alert I could have avoided this ugly re­petition.

Subroutine 500 displays instructions fairly well with some good graphical explanations of eater moves.

Subroutine 600 should be decorated with REMs to stand out better - it loads the screen with digits initially. 700 puts the eaters in position, defines their plot character and resets their scores.

800 checks to see if we have a winner or an over-run. The remaining subroutines are odds and ends used elsewhere.

Numbereater is closer to a bar game than most of the other programs in this book, which usually have some moral attached to them of a logical, mathematical, or philosophical nature. Perhaps that is why Numbereater

is more in demand than they are, at party time.

77

Page 83: The Alien, Numbereater and other Programs for Personal Computers

10 REM------NUMBER-EATER GAME------15 REM 20 GOSUB 500: REM INSTRUCTIONS 25 GOSUB 700: REM INITIALISE EATERS 30 GOSUB 600: REM FILL UP SCREEN 40 GOSUB 200: REM GET A CHAR 45 GOSUB 400: REM MOVE AND EAT 50 GOSUB 800: REM TEST SCORES 60 IF D$<>"TRUE" THEN 40 70 PRINT "ANOTHER GAME? PRESS Y OR N" 80 GOSUB 200: REM GET A CHAR 90 IF A$="Y"THEN 25 95 IF A$="N"THEN 99 96 GOTO 80 99 STOP 197 REM 198 REM------GET A CHARACTER 199 REM 200 GET A$:IF A$=""THEN 200 210 RETURN 397 REM 398 REM------MOVE AND EAT 399 REM 400 X1=0: Y1=0: X2=0: Y2=0: REM INCREMENTS TO EATER POSITIONS 410 IF A$="Q" OR A$="W" OR A$="E" THEN Y1=1 420 IF A$="7" OR A$="8" OR A$="9" THEN Y2=1 430 IF A$="Z" OR A$="X" OR A$="C" THEN Y1=-1 440 IF A$="1" OR A$="2" OR A$="3" THEN Y2=-1 450 IF A$="Q" OR A$="A" OR A$="Z" THEN X1=-1 460 IF A$="7" OR A$="4" OR A$="1" THEN X2=-1 470 IF A$="E" OR A$="D" OR A$="C" THEN X1=1 475 IF A$="9" OR A$="6" OR A$="3" THEN X2=1 477 IF X1=0 AND Y1=0 THEN 490 480 U1=U1+X1: V1=V1+Y1: IF Ul<l THEN Ul=l: REM CHECK TO KEEP IN VDU 482 IF U1>40 THEN Ul=40 485 IF V1>40 THEN V1=40 486 IF V1<1 THEN Vl=1 488 GOSUB 920: GOSUB 970: V=P: GOSUB 900 489 T1=Tl+N: T1$=RIGHT$(" "+STR$(T1),4) 490 IF X2=0 AND Y2=0 THEN 498 492 U2=U2+X2: V2=V2+Y2: IF U2<1 THEN U2=1 493 IF U2>40 THEN U2=40 494 IF V2<1 THEN V2=1 495 IF V2>40 THEN V2=40 496 GOSUB 925: GOSUB 970: V=Q: GOSUB 900 497 T2=T2+N:T2$=RIGHT$(" "+STR$(T2) ,4) 498 PRINT"q SCORE= ";T1$:PRINT"& SCORE= ";T2$ 499 RETURN

500 FOR I=1 TO 12: READA$: PRINT A$: NEXT 505 S=32767+24*40 507 GOSUB 950: GOSUB 200 508 RETURN

Figure 58. NUMBEREATER Program, first part

78

Page 84: The Alien, Numbereater and other Programs for Personal Computers

510 DATA"3 *** NUMBER EATER GAME ***" 520 DATA" FOR 2 PLAYERS" 530 DATA" PLAYER q PLAYER &" 540 DATA" MOVES HIS EATER MOVES HIS EATER" 550 DATA" Q W E 7 8 9" 560 DATA" 1> I I>/" 570 DATA" A-q-D 4-&-6" 572 DATA" lbl lb/" 573 DATA" Z X C 1 2 3" 574 DATA" EG W MOVES q UP" 578 DATA" FIRST TO EAT EXACTLY 100 WINS" 579 DATA" IF YOU SCORE OVER 100, BACK TO O" 600 FOR I=1 TO 100 610 X=1+40*RND(6): Y=20*RND(6) 620 L=S+X-40*Y 630 IF PEEK(L)<>32 THEN 650 640 POKE L,INT(RND(6)*10)+48 650 NEXT 660 RETURN 700 P=ASC("q")-128:Q=ASC("&")-64 705 PRINT"3": U1=14: V1=12: U2=26: V2=12 715 V=P: GOSUB 920: GOSUB 900: REM PLOT THE EATERS 717 V=Q: GOSUB 925: GOSUB 900 718 T1=0: T2=0 720 RETURN 797 REM 798 REM------TEST SCORES FOR WINNER 799 REM 800 A$="@": IF T1=100 THEN A$="q" 805 IF T1>100 THEN T1=0 810 D$="FALSE": IF T2=100 THEN A$="&" 815 IF T2>100 THEN T2=0 820 IF A$="@" THEN 850 830 PRINT"CONGRATULATIONS, ":A$ 840 D$="TRUE" 850 RETURN 897 REM 898 REM--------PLOT AND CALC POSITION SIRS 899 REM 900 POKE Z,V:RETURN 920 Z=S-40*V1+U1:RETURN 925 Z=S-40*V2+U2:RETURN 950 PRINT:PRINT"** HIT ANY KEY TO CONTINUE":RETURN 967 REM 968 REM-------GET NUMBER OFF SCREEN 969 REM 970 N=PEEK(Z)-48:IF N<O THEN 980 972 IF N>9 THEN 980 975 GOTO 985 980 N=O 985 RETURN

READY. Figure 59. NUMBEREATER Program, second part

79

Page 85: The Alien, Numbereater and other Programs for Personal Computers

Appendix: Amendments to Programs for later PETs with revised ROMs

The programs in this book are for the original 8K PET. Later PETs have a revised operating system and a different allocation of 'zero page' ( 0 to 255 ) bytes. Most programs will run unchanged, but where use is made of the zero page or machine-code subroutines in ROM, changes may be re­quired.

GIP. Remove line 40, which inverts the effect of the shift key, because in the new PET, the shift key works normally as it stands - press shift down for upper case, release it for lower case like a normal typewriter. In line 72, alter 525 to 158 and in 73, alter 526 to 623. These are the locations of the keyboard buffer index, and the first byte of the key­board buffer, respectively.

In SPIRAL, the 6502 program will still work, but in HISTOGRAM, the x -axis scale and title ( 'GOLD 1980' ) disappear because the program uses bytes in the zero page as the base for indirect addressing, and the new PET takes these bytes as control parameters for output, so that PRINTs go wrong. To cure this, references to bytes BO and Bl need changing to SE and SF respectively. In Figure 34, 'Assembler program to plot/erase small squares', the corresponding amendment to the Assembler source code is to change lines 12 and 13 to assign LO and HI to bytes 94 and 95. Revised DATA statements for inclusion in HISTOGRAM follow:

999 REM----- PLOT SMALL-SQUARE 6502 PROGRAM REVISED FOR NEW PET ROM 1000 DATA 205B03208C0320A70320B9036037130303207E7CE27B61FFEC6C7FE1FB62FCFE 1001 DATA AOCE4703AD470329018D49034E4703A9018D4A0338A932ED48038D48032901FO 1002 DATA 05A9048D4A03AD4903F0030E4A034E480360AD48030AOA186D4803855EA90006 1003 DATA 5E2A065E2A065E2A0980855F60AC4703Bl5EA200DD4B03F005E8E010DOF6608A 1004 DATA OD4A03AABD4B03915E60202020202020525453220D22203732202E454E44220D 1005 DATA 0000

SUPERLIFE. As it stands, the program will terminate prematurely with 'THAT'S ALL, FOLKS' because the 6502 machine-code 'count-neighbours' routine fails. You can use the BASIC 'count-neighbours' routine by tak­ing out GOTO 940 in line 900, but this slows down the program consider­ably. To adjust the machine-code program, replace the calls to FLPINT and INTFLP to the revised locations in the new ROM, and alter references to bytes B3, B4, BS to read 61, 62, 63. Be aware that the Commodore manual P/N 32856-3 has errors in it.

REMOVER. If you are the proud owner of the larger PET, you may well have enough memory not to need this little program for condensing BASIC by cutting out spaces and REMs. If you do need it, then refer to Figure 41, 'REMOVER program written in 6502 Assembler', and alter lines 2- 5 to use new free locations in the zero page ( e.g. one of the floating point accumulators). In line 6 and 7, the addresses 124 and 125 need changing to 42 and 43, and in 8 and 9, bytes 233 and 234 in the new PET are involved in video control so should be changed to use free bytes. Lines 10 and 13 should be changed to address the revised 'start of BASIC text', i.e. 40, 41.

When delving into the innards of the PET, you may try to PEEK parts of the ROM which Commodore do not want you to see, in which case PEEK gives you zero: to help you, here is a little program which plants a tiny 6502 program in the second cassette buffer to transfer a byte from the protected area to an unprotected one, and to display its contents in decimal and HEX:

80

Page 86: The Alien, Numbereater and other Programs for Personal Computers

0 REM------ DISPLAY MEMORY IN SPITE OF COMMODORE TRAP 1 REM 5 A$="0123456789ABCDEF" 10 FOR I=O TO 200 12 Q=l3*16*256+167+I: REM ADDRESS= $DOA7 •••• 13 P=Q:GOSUB 300: REM SPLIT ADDRESS INTO 2 BYTES 15 POKE 826,173: REM LOA 16 POKE 827,P2: REM LEAST SIG BYTE OF ADDRESS 17 POKE 828,Pl: REM MSB 19 POKE 829,141: REM STA 20 POKE 830,72: REM LSB OF 840 21 POKE 831,3: REM MSB OF 840 22 POKE 832,96: REM RETURN 25 SYS 826: REM EXECUTE CODE 27 P=PEEK(840) :GOSUB 200: REM SPLIT HEX INTO 2 NIBBLES 30 PRINTQ, P," "; MID$(A$,Pl+l,l);MID$(A$,P2+1,1) 40 IF P=l80 THEN STOP: REM FIND B4 50 NEXT 60 STOP 200 Pl=INT(P/16) :P2=P-16*Pl:RETURN 300 Pl=INT(P/256) :P2=P-256*Pl:RETURN

READY. RUN

53415 165 AS 53416 176 BO 53417 201 C9 53418 144 90 53419 144 90 53420 9 09 53421 169 A9 53422 153 99 53423 160 AO 53424 208 DO 53425 32 20 53426 45 20 53427 219 DB 53428 208 DO

Note. The above program would be marginally faster but much less clear, if the POKEs loading the machine code program were moved outside the loop when the value POKEd is not affected by I.

81

Page 87: The Alien, Numbereater and other Programs for Personal Computers

Index ABS Fig 10 ARCTAN 68 ASC Fig 6 Actual-vs-Predicted-Descents Fig 7 Alien,the(XXthCent-Fox) 67 Alien-program Fig 51 Alien-results Fig 50 Apple 2 Approaching-the-l::ioon Fig 11 Assembler-program Fig 34 Assemblers 39

BASIC-dangers-of-line-numbers 33 BCC Fig 41 BEQ Fig 41 BNE Fig 41 BYTE. 41 Bigram-substitution Fig 48 Block-diagram Fig 44 Build-Life-Lists Fig 27

CHR$ Fig 52 CLC Fig 41 CLEAR-program Fig 55 CMD 12 CMP Fig 41 Calculus 6 Chess 4 Concatenate Fig 19 Conways-Game-of-Life Count-Life-Neighbours Cursor-home-made Fig

26 Fig 28 23

DATA Fig 9 DEC Fig 41 DIM Fig 5 DIM(4,4)=25elements DIM-less-than-11 DIMA%(255) Fig 22 De-Lesseps 4

63 45

Decimal-to-Hexadecimal-conversion Decision-tables Fig 43 Decyphering Fig 47 Delay-loop Fig 51 Display-Life-Blob Fig 26 Documentation 58 Documentation Fig 33 Double-density-Histogram Double-density-plotting Duncan 41

Fig 32 39

Encyphering-results Fig 46 Escher 6 Expressions-well-formed 8

FOR-NEXT Fig 9 FRE Fig 52

Fig 42

83

Page 88: The Alien, Numbereater and other Programs for Personal Computers

GET Fig 18 23 GET Fig 23 GIP-Logic-Block-Diagram GIP-presentation Fig 54

Fig 53

GOSUB 4 GOSUB 8 GOSUB 10 GOSUB Fig 8 GOTO-considered-harmful General-Input-Program

34 Fig 52

God-playing 26 Godel 6 Graphics-cursor-control Graphics-drawing-maps

Fig 16 Fig 15

Graphics-symbols 20 Graphics-~ymbols Fig 51 Grav~tation 13 Greenfly-behaviour 49

Hex-to-decimal-conversion Hexadecimal 37 Hexadecimal 41 Histograms 39 Hofstadter 6

IF-THEN-(ELSE?) 17 INC Fig 41 INPU'!' Fig 49 INT Fig 5 Index-register-on-6502 37 Indirect-addressing 54 Initialisation Fig 9 Integers'>:%' 45 Integers-% 27 Fig 22 Integers-use-of-% 22

JII~P 44 JSR Fig 41 Justification 23

KiiOC 72 Keyboard-buffer 75 Keystroke-test-for 23

LDA Fig 41 LEFT$ 22 Fig 16 LEN Fig 33 LSR 41

Fig 33

Letting-message-sink-in Fig 23 Life-Changes Fig 24 Life-Deletions Fig 25 Life-Initialise Fig 23 Li fe-prograr.t-rtmning Fig 21 List-processing 47 List-processing 33 Load-code Fig 37 Loader-for-REMover Fig 42

84

Page 89: The Alien, Numbereater and other Programs for Personal Computers

MID$ 4 1-iiD$ Fig 5 1-iachine-language 36 Machine-language-count-neighbours ~~achine-language-loader Fig 31 Main-Logic-of-Life Fig 22 Main-Logic-of-Orbit Fig 8 Mods-to-Brackets-Program Fig 6 Moon-Fly-by Fig 14

NEXTI,J Fiq 18 NOT-EQUALS-<> Fig 22

OPEN 12 OHA 43 OUT-OF-MEMCRY 50 OUT-OF-~~~ORY 6 OUT-OF-MEMORY 8 Orbit-achievement 20 Out-of-Fuel! Fig 13

PEEK 11

Fig 30

PET-screen-location-calculate 29 POKE 11 POKE-aug 52 Palindro~es Fig 1 3 Parameter-passing 47 Pascal 3 Pascal-Triangle-results Fig 20 Pascals-Triangle-program Fig 19 Pattern-recognition 45 Pet 2 Playfair 63 Playfair-program Fi9 49a Playfair-program-2 Fig 49b Plot,use-SYS Fig 36 Pointers-into-arrays 27 Fig 22

P.EAD Fig 35 F.EAD Fig 9 REI~ 50 REMover-program REMover-results ROL 43 RTS Fig 41

Fig 41 Fig 40

Fig 49 25

68

Randomise-a-string Recurrence-relation Relay,~olid-state Results-of-numbereater Rocket-bur~ Fig 18 Russell-Bertrand

Fig 57

6

SEC Fig 41 SPC Fig 52 SQR 16 Fig 15 STP... Fig 41

85

Page 90: The Alien, Numbereater and other Programs for Personal Computers

:JTEP Fig 27 STR$ 12 Fig 5 STR$ Fig 16 23 SYS Fig 33 SYS Fig 40 Simulation-Loop Fig Skimming-the-craters Speed-techniques Speed-techniques Speed-techniques Spiral-progll'am Fig Star-wars 55

10 Fig 12

45 49 32 35

Subroutine-Design 15 Subroutines-one-entry,one-exit Superfluous-code Fig 17 Superlife-structure Fig 29

TAB 20 Fig 15 TAB Fig 19 Trapdoor/Knapsack-encryption

USR Fig 28 USR 36 unreadable-program Fig 39 Upper/Lower-case 71 User-port 68

VAL Fig 18 23 VIA 68 Variable-names 47

Zaks 41 Zero-page 54

34

63

86


Recommended