- "PCW: Streamlined BASIC"
   Geoffrey Childs
   PCW-World, 1990

A comprehensive guide to streamlining Mallard BASIC on the Amstrad PCW range

(Retyped by Emmanuel ROCHE.)


Published  by  PCW-World, Meadway Court, Bloomfield Street  North,  HALESOWEN, 
West Midlands B63 3RE, England.

Copyright 1989 (c) PCW-World All rights reserved. No part of this  publication 
may  be  reproduced  in  any  form  without  the  written  permission  of  the 

ISBN 0 9515486 0 3

First Published October 1989
Second Edition July 1990

A disc containing the programs printed herein is supplied with this book.

No  liability whatsoever will be entertained by the publishers or  the  author 
for  any  damages, consequential, indirect, incidental or  special,  resulting 
from  the  use  of the material contained in this  book  or  the  accompanying 
program disc.

AMSTRAD is the registered trademark of AMSTRAD Consumer Electronics plc.
CP/M Plus is the registered trademark of Digital Research Incorporated.
Mallard  BASIC,  LOCOMOTIVE  and  LocoScript  are  registered  trademarks   of 
Locomotive Software.
All trademarks acknowledged.


My sincere thanks are due to my many correspondents and friends who have given 
me  ideas,  answered  my  queries and made helpful  comments  which  have  all 
contributed  to the preparation of this book. In particular, I would  like  to 
thank  David  Wilson,  who has made numerous suggestions (some  of  which  are 
printable)  and  assisted with the final editing. I am also very  grateful  to 
Gerry  Austin  of PCW-World, who has managed to publish this book  within  two 
months of our initial meeting -- incredible!

Geoffrey Childs, October 1989.

Table of Contents

Part 1


Chapter 1: BASIC matters

1.1 Purpose
1.2 BASIC extensions
1.3 Your own style
1.4 Planning

Chapter 2: Everyday problems

2.1 Escape sequences
2.3 Randomizing and the clock
2.4 Only 31K?
2.5 Integer variables, etc
2.6 Loops
2.7 Approximations
2.8 AND, OR, XOR
2.9 IF...

Chapter 3: Input techniques

3.1 INPUT, INKEY$, etc
3.2 INKEY$ or...
3.3 Filename checking
3.4 Date checking
3.5 Checking input
3.6 ON x GOTO or GOSUB
3.7 Menus

Chapter 4: Files

4.1 Sequential, random, or Jetsam?
4.2 Saving memory
4.3 File transfers

Chapter 5: Editing and debugging

5.1 Editing
5.2 Debugging
5.3 AUTO and RENUM
5.4 Subroutine libraries?
5.5 Mallard 'bugs'

Chapter 6: Style

6.1 To REM or not to REM
6.3 Long or short lines?
6.4 Protection and OPTION RUN
6.5 Where does one put subroutines?
6.6 GOTO taboo?

Chapter 7: It pays to increase your word power

7.1 FIND$
7.2 DEF FN
7.3 MID$ and VARPTR
7.4 LSET and RSET
7.6 CHR$ and ASC
7.8 LIST

Chapter 8: Writing for all PCWs

8.1 Mallard 1.29 and 1.39
8.2 Using the discs available
8.3 Machine code and compilers

Chapter 9: Intermission!

9.1 Can you INPUT a function?
9.2 UDG program
9.3 Sound and OUT

Part 2

Chapter 10: A magical mystery tour

10.1 The transport
10.2 Equipment
10.3 The map

Chapter 11: Graphics

11.1 Binary patterns
11.2 The visible screen
11.3 The screen characters
11.4 Roller RAM
11.6 A new screen clear
11.7 A little frivolity!
11.8 Plotting a pixel
11.9 Saving screens
11.10 Altering the character set
11.11 Further graphics

Chapter 12: Sound

12.1 If music be the food of love...

Chapter 13: BASIC and CP/M

13.1 Input and output
13.2 DMA and FCB
13.3 Reading and writing files
13.4 The file directory
13.5 Other BDOS calls
13.6 Writing CP/M files
13.7 Combining BASIC and CP/M Plus

Chapter 14: Useful addresses...

14.1 The Mallard interpreter
14.2 CP/M Plus addresses

Chapter 15: Going places

15.1 Accessing the unaccessible
15.2 CALLing to BIOS
15.3 Screen dump
15.4 Where is the cursor?

Chapter 16: The keyboard

16.1 Keyboard information
16.2 Setting a key
16.3 Grandpa

Chapter 17: The 'M' disc

17.1 Screen saving

Chapter 18: Direct disc access

18.1 Format
18.2 Disc editor

Chapter 19: Interrupts

19.1 Excuse me, I'll only be a microsecond!
19.2 The routine timer


1: Program disc
2: Turnkey discs
3: Documentation for DWBAS
4: Multiple POKE
5: Disassembler
6: Menu subroutine
7: Multiple input
8: Bibliography and discotheque
9: Printer fonts
10: LNER 'Mallard' listing
11: Notes to the second edition


Part 1


You  may  be a very good programmer, you may just think you are,  or  you  may 
realise  that  you  are only a beginner. You may  have  written  thousands  of 
programs...  or  just the one. It does not matter in the least. The  less  you 
know, the more you can learn from this book.

Will  this  book  help you to write spectacular  programs  with  mind-boggling 
graphics?  Or  will  it  just help you to write  a  program  for  your  weekly 
budgeting  that works efficiently? The answer lies solely in your hands.  This 
book  will  give  you the means to produce programs with  all  the  bells  and 
whistles. It will also help you to do the much simpler programming tasks well. 
What it will NOT do is to teach you how to PRINT "Hello". Nor will it give the 
syntax  for every Mallard BASIC word. You need an elementary tutorial for  the 
former, and a manual for the latter.

Part  1 is about the things that you CAN officially do with Mallard BASIC.  It 
is   hoped  that  the  discussions  will  improve  the  proficiency  of   your 
programming,  so that the simple things are done effectively. Part 2 is  about 
the things you CANNOT do with Mallard BASIC. As we shall see, there is no such 
word as CAN'T! Admittedly, we shall have to make some recourse to machine code 
in Part 2: but have no fear, you will not be asked to understand it. Any BASIC 
programmer can use code routines that have already been written.

The final section is a series of appendices. This includes a simple  extension 
to  Mallard BASIC and a few other programs that do not conveniently  fit  into 
the  text. There are plenty of short programs and routines that  are  included 
and explained in the context of the text. Each of these will be labelled  with 
a name such as C4P2 (short for Chapter 4 Program 2). These can be found on the 
program disc supplied with this book.

In  a book about computers, it is impossible to avoid 'jargon'  completely.  I 
hope that most of it will be explained as we progress. It may be helpful  just 
to  explain  a few words immediately. MEMORY can be thought of  as  the  brain 
cells of the computer. Each cell of memory (there are over 500,000 of them  in 
the  Amstrad  PCW8512) contains a number 0-255 that the  computer  'remembers' 
until  it is changed, or until the computer is switched off. CODE is  an  all-
purpose  word  that  means  symbols, letters, words,  and  language  that  the 
computer  can operate upon (given a little bit of help by what is  already  in 
its memory). The PROCESSOR is the electronic device that turns the numbers  in 
memory into simple activities that the computer can carry out. MACHINE CODE is 
the  code that the processor can understand. A programming  LANGUAGE  converts 
the code that a programmer writes into code that the computer can use. Mallard 
BASIC is called an INTERPRETER as it does that conversion, line by line,  each 
time  a  program runs. Other languages use a COMPILER which turns  a  complete 
program (SOURCE) into a new program (OBJECT). The object program is in machine 
code. We shall not be much concerned with compilers. RESERVED words are  words 
that have a special meaning to the computer in languages such as BASIC. PRINT, 
RANDOMIZE,  and SQR are examples of reserved words in BASIC. DO is a  reserved 
word in the Pascal language, but means nothing in BASIC.

If you did not receive a program disc when you bought the book, please let  us 
know at PCW-World ('Cotswold House', Cradley Heath, Warley, West Midlands  B64 
7NF) and we will provide a disc free of charge on receipt of some evidence  of 
the sale.

Chapter 1: BASIC matters

Just  before  the second world war, LNER ("London &  North  Eastern  Railway") 
engine number 4468 achieved an all-time speed record for a steam engine of 126 
mph (203 km/h) between Grantham and Peterborough. It could not have been  done 
without  'streamlining'  or a competent driver and fireman.  Number  4468  was 
named  "Mallard", as is the BASIC on your Amstrad PCW. Mallard BASIC has  also 
been  designed  to  be very fast and, when you program with it,  you  are  its 
driver and fireman. The 'streamlining', I hope, will be found in the following 

1.1 Purpose

This  book  is not intended to be a user manual for Mallard BASIC, or  even  a 
tutorial.  It  is intended for Amstrad PCW users who  have  experimented  with 
Mallard BASIC and attempted to write some serious, even if simple, programs of 
their  own, but would like to move on to bigger and better ones. If  you  have 
not reached this stage, I suggest you study some tutorial material first. Your 
first  choice  will,  obviously, be the official manual, and  a  book  by  Ian 
Sinclair has been generally recommended. For those preferring to learn at  the 
keyboard,  'David  Wilson  Computronics' have produced  a  comprehensive  disc 
entitled "BASIC Tutorial".

As  I  develop  my approach, I will not be afraid to PEEK and  POKE  about  in 
memory from time to time, but I will not assume that you have any knowledge of 
machine  code programming -- or even that you want to descend to  that  level! 
Very  occasionally,  a machine code sequence will be included  in  an  example 
routine, but you will not need to understand it.

My central theme will be programming style, though some might think that I  am 
the last person on Earth who ought to be writing about that (ROCHE>  Including 
me, since Geoffrey Childs *NEVER* indents his programs!). If someone wanted to 
compliment  my  programming, I would accept -- with typical  modesty  --  such 
words as "knowledgeable", "ingenious", "economical", and even "eccentric". But 
not  even my best friends -- nor, for that matter, my worst enemies  --  would 
call me a stylish programmer. However, what we are considering is your  style, 
not  mine. There are certain aspects of programming that I consider to be  bad 
programming  rather  than  bad style and, in these cases,  I  shall  be  quite 
dictatorial, telling you a few things that you must not do. Apart from that, I 
hope  that  you will accept most of what follows as suggestions,  rather  than 
instructions.  Very often, the question is one of speed of operation,  or  the 
comfort  of  the  user, which is what good footplate  crew  have  always  been 
concerned about.

Any form of programming is a skill and, however individual a skill may be, the 
best practitioners will try to learn from others. If you think that you always 
program  perfectly,  who am I to disagree? You may find some of my  ideas  and 
methods  reprehensible  --  again, I will not disagree. What I  can  claim  is 
experience -- the fact that I wrote "Lightning BASIC" proves that -- and  that 
most of my programs do work, eventually.

1.2 BASIC extensions

Mallard is a powerful and fast implementation of BASIC. It is fair to say that 
the  more I have used it, the more I like it. I did find it rather strange  at 
first,  after using Microsoft and Xtal BASICs on other machines. However,  any 
new system needs patience before it becomes familiar.

Having  said  this, there is no question that Mallard is extremely  clumsy  at 
doing  a few simple things, the most obvious of which is clearing the  screen! 
(ROCHE>  BASIC,  and CP/M, were first used on computers which had  no  screen, 
only  an  ASR-33 Teletype through which all interaction  with  BASIC/CP/M  was 
done.  So, no screen = no CLS. Anyway, who needs a CLS when pressing  [RETURN] 
the  appropriate  number of lines is enough to clear the screen?) There  is  a 
method  behind Mallard's madness. It is not that Locomotive were incapable  of 
devising a CLS which would have done the job. By using control codes,  Mallard 
files  become portable when saved in ASCII form, and can be used on  computers 
other than the Amstrad PCW. (ROCHE> I have been using Mallard-86 BASIC for MS-
DOS  for 10 years, now, and have *NEVER* used ASC files, only BAS  files  when 
transfering my old Amstrad PCW programs...)

Your  masterpiece (and mine) are probably only intended to run on the  Amstrad 
PCW. (ROCHE> I started as a COBOL programmer on IBM Mainframes. Then  ventured 
into minicomputers. Then finally used micro-computers (which went from 8  bits 
to  16  bits,  then 32 bits!). For me, Mallard BASIC is a  high-level  way  of 
porting  my  programs: they run under CP/M Plus, MS-DOS, and in  a  "DOS  Box" 
under  Windows.) It makes sense to make a few extensions to Mallard BASIC,  so 
that the simple jobs can be done quickly.

Much of my programming on the Amstrad PCW has been devoted to writing  various 
extensions  to  Mallard  BASIC, so that not only does it do  the  simple  jobs 
easily, but becomes more powerful generally.

I  will admit that I do most of my BASIC programming using  "Lightning  BASIC" 
but,  then, having written it myself, I have not had to pay for it! You  would 
not  like it if, having raided your piggybank to buy this book, I told you  to 
send  24.95 Pounds to CP Software to buy Lightning BASIC. (But, of course,  it 
is worth every penny...)

DWBAS is a much shorter extension (also written by me) -- this can be RUN  and 
LISTed  from  the program disc. You may have this for nothing. I do  not  like 
typing  in  programs with lots of figures, that is why I have  provided  DWBAS 
(and  the  other  programs  in this book) on disc. If  you  have  the  Amstrad 
PCW9512,  use  DW139  instead, this is also on the program disc.  This  is  an 
amended  version  that runs with Mallard-80 BASIC Version 1.39.  (The  Amstrad 
PCW8256  and  PCW8512 use Mallard-80 BASIC Version 1.29.) The  explanation  of 
these extensions is given in Appendix 3. The programs in the text do not  rely 
on DWBAS, but those in Appendices 6 and 7 do need DWBAS loaded first.

One  important feature of DWBAS (and Lightning BASIC) is a multiple POKE.  The 
command  POKE  50000,2,3,4 will POKE 2 into 50000, 3 into 50001,  and  4  into 
50002.  I find this so important that I shall INSIST that you have a  multiple 
POKE when we come to Part 2 of the book. If you refuse to use DWBAS,  Appendix 
4 gives a briefer program which will install this feature. Naturally, you will 
not need this if you use DWBAS.

1.3 Your own style

Part  1  of  this  book is intended to help you  develop  your  own  style  of 
programming. There are usually many ways of achieving the desired result in  a 
program.  Often,  the  programmer has to make a choice  between  economies  in 
memory  and  speed  on the one hand, and attractive  presentation  and  easily 
readable   programs  on  the  other.  Programming  style  generally   develops 
subconciously,  but it is not a bad idea to have an introspective  examination 
of  one's programs from time to time. In this section, we have  a  preliminary 
look at some of the questions that you might consider.

Writing  computer programs has parallels with writing English. It is  easy  to 
identify bad style, but good style is not so clear cut or uncontroversial.  In 
most  of  our  literary  works, we would try to  avoid  spelling  sausages  as 
'sosiges', but it would not matter if we put 'sosiges' on our weekly  shopping 
list!  In an idle quarter of an hour, last week, I used a computer program  to 
work out the Daily Telegraph Brain Teaser. It does not matter in the slightest 
how badly the program was written, provided it works!

Another parallel with English is that the style of a computer program  depends 
on the intended user. You would not write a love letter in the same style as a 
technical  report, and it is unlikely that you would write a "Space  Invaders" 
game in the same style as an accounts system.

Clearly, there are some general rules that all programmers should follow,  and 
they  can  be summarised in one sentence. Write your program in a  style  that 
will  be  suitable for the people you expect to use it.  The  balance  between 
lengthy  prompts, excessive input checking and confirmation on the  one  hand, 
and  a  fast  flowing program on the other, must be  made  with  the  expected 
experience of the average user in mind. Decorative screens and musical jingles 
(not  so  easy  on  the Amstrad PCW) must be  balanced  between  pleasure  and 
irritation  potential! It is sometimes hard to remember that the person  using 
your  program,  probably  a philistine, will not be  admiring  your  beautiful 
programming skills, but using your program as a means to an end.

Having  made these general didactic points, it is time to emphasise  the  main 
point I want to make in this section: DEVELOP YOUR OWN STYLE.

What  is right for you, may not be right for me, and vice versa. If  you  find 
that  you can write programs that work well without bothering about  structure 
and subroutines, then write that way -- at least until you find that it  might 
be wiser to modify your views. If you find that planning on paper and having a 
complete knowledge of exactly what your program will do is essential -- do  it 
that  way. Academic writers about computer programming talk some sense, but  I 
think that they talk a lot of rubbish, too!

I  am going to finish this section by asking a number of questions.  They  are 
question  which you may feel strongly about, and your answers may be  opposite 
to mine. You may feel that the answer is sometimes 'yes' and sometimes 'no'. I 
hope that some will be questions that you have not even considered. In a short 
section  like this, it is impossible to cover all the questions one might  ask 
about  style. Provided you are thinking about these propositions,  it  matters 
not  whether  you  become more pig-headed in your views, or  more  willing  to 
modify them.

Should variable names be long: "age.of.employee" or short: "a"?

Do you religiously, occasionally, or never put in REMs?

Do you leave blank lines, such as "200 :", to make your listings look pretty?

Should  documentation  be non-existent, in REMs, on-screen, or in  a  separate 
file or booklet?

Do you use "Press any key..." or a specific "Press SPACE..."?

Do you always use INPUT, even for one-letter replies?

Do you believe in right-justifying or centring screen printing?

Do you make sure the user sees your name on any programs you write?

Do you have any little habits by which a program you wrote would be identified 
as likely to be yours?

Should  you  avoid  POKEs or CALLs that make your programs  machine  or  model 

Do you use reverse video often, occasionally, or never?

Do  you  write your subroutines before the rest of your program,  or  whenever 
they crop up?

Do you think that you make too little or too much use of subroutines?

Do  you  like  or dislike programs where the menu choice  is  made  by  cursor 
control (or a mouse), rather than by pressing a key?

Is it better to keep most program lines to a single statement?

What  are your feelings about GOTO? That it should be never used. That is  has 
occasional uses. That you do not care what the academics think, and it is just 
about your favorite BASIC word!

Do you use integer variables whenever possible, occasionally, or never?

Do  you  RENUM  when a program is complete, or do you leave  numbers  as  they 
stand, so that you may be able to recall them better later?

Do you ever use DEF FN, apart from escape sequences? Or PEEK, POKE, CALL,  and 

Do you use ON ERROR throughout a program, for occasional bits where a specific 
error could be expected, or never?

Do you protect your programs? If not, do you make your listings as easy (or as 
difficult!) as possible for the user to follow?

Which type of files do you use most often: sequential, random, or Jetsam?

Do  you think Jetsam is the best thing about Mallard BASIC, could be  used  on 
occasions, or is too complex to try to understand?

What  is  your attitude to a finished program that works? "I am not  going  to 
touch it again", or "I will take every opportunity to gild the lily"?

Do you consider it bad programming if the screen scrolls during operation?

Some  of the questions will not be discussed any further, but there are  other 
questions on which I do have definite views, and which we shall think about in 
more  detail.  Where  this happens, I shall be happy to  preach  to  heathens, 
doubters, or the converted!

1.4 Planning

If  you  are a competent pianist, it is possible to sit down at a  piano,  put 
your hands on the keys, have no idea what you are going to play, and improvise 
happily  and  tunefully. You cannot do this with a computer. All  programs  do 
need  an  element  of planning. At the very minimum, you must  know  what  the 
program aims to achieve.

Flowcharts  were  once one of my pet hates. Perhaps I do not  read  the  right 
books  these days, but thankfully we do not hear so much about them, now.  The 
idea of a flowchart is to put into words the various stages of a program,  and 
use  symbols and arrows to define its possible courses. For a simple  program, 
this  seems a waste of effort and, for a complex program, it is  difficult  to 
imagine  the  size  of paper that you would need! I  would  find  drawing  the 
flowchart much harder than writing the program -- which is not the purpose  of 
the  exercise! I know some people do find them helpful. If you like  them,  do 
not let me stop you using them. You are probably more methodical than I.

Some methods of planning are needed. If I am writing a Mallard BASIC  program, 
I  usually  code  at  the computer. Occasionally, I may use  the  back  of  an 
envelope  for  a section of code that looks tricky. I may write a  very  rough 
menu  outline  for  a long program on paper. If I am writing  a  machine  code 
routine, I usually write it in assembler on an envelope, and translate it into 
code at the machine. Not recommended, unless you also are in an advanced state 
of madness! The stationers do not make a fortune out of me. I appreciate  that 
most people would disagre with so little paper planning. Ask yourself if  more 
time  spent on paper planning would make the eventual writing easier  or  more 
efficient. Only you can decide.

You may have kindly thoughts like: "Yes, but you are such a genius that you do 
not  need to plan." Or unkind ones like: "No wonder your programs turn out  so 
badly." Wrong on both counts. I do plan: I probably spend more time on it that 
you do. If an idea for a substantial program comes to mind, I do not sit  down 
at  the  computer to write it. I carry the idea, maybe for months,  and  think 
about  it.  I do nothing at the computer until I know that I  have  sufficient 
time  to  write the program with a minimum of breaks. By the time I  start,  I 
have  a clear outline plan in my mind. While I am writing the program, I  will 
be  thinking about it and the little extras that can be introduced to  improve 
on  the outline. When the program is written, I hope to have nothing  more  to 
do. Usually, this is optimistic because it is often someone else who finds the 

'Structured programming' is a computer buzz-word. To some people, it is closer 
to  a religious utterance. A structured program is contained in modules. On  a 
computer such as the BBC, these modules are called PROCEDURES. On the  Amstrad 
PCWs,  we have to make do with subroutines, which are nearly the  same  thing! 
The  procedures are written, and all the main program does is to call them  in 
the  correct sequence. My advice is not to make structured programming into  a 
religion,  but  make  good use of subroutines, so that your  programs  have  a 
semblance of structure -- even if served with a helping of spaghetti!

If I use any formal technique, it is probably closer to the 'top-down'  method 
than anything else. I usually a program at line 5000, where I put the  general 
purpose subroutines -- some will have been pinched from other programs. I  may 
write  some more specific subroutines, such as filing ones, starting  at  line 
6000, which may only need to be chained to certain options on the menu. I  use 
7000+ for error traps. The main program is divided into sections, starting  at 
line  1000,  2000,  etc. I write the program a section at  a  time,  when  the 
subroutines  have  been  written.  Sometimes, I also  use  lines  above  8000. 
Eventually,  the  main  menu and housekeeping goes at  the  beginning  of  the 

There is no magic in this particular numbering system, but there is plenty  of 
point  in establishing a pattern for yourself and sticking to it, if only  for 
the fact that anything done consistently is more easily remembered.

However  well  you  plan, you will probably spend at least  as  much  time  on 
testing  and debugging your program as on writing it, but we will leave  these 
little treasures until later.

Chapter 2: Everyday problems

In this chapter, we are going to look at some Mallard BASIC commands that most 
of  you  will  have  used. Familiarity can breed contempt,  and  good  or  bad 
programming is often more evident in the simple everyday operations that  most 
programmers will be using frequently. The topics may seem disjointed, but  the 
common  theme is that we should think about the routines that we know  we  can 
program, as well as those that we know will offer a new challenge.

2.1 Escape sequences

One  of  the first things most of use notice about Mallard BASIC is  that  you 
cannot clear the screen with a simple CLS (or something similar). I have  come 
to  respect Mallard in many ways, but I still think that this  is  ridiculous, 
even though the reason is that Mallard BASIC is meant to be portable.

If  you are using DWBAS, you will be happy not having to use escape  sequences 

        PRINT CHR$ (27) + "E" + CHR$ (27) + "H"

any more.

This  will be the case if you only want to program for yourself. Some  of  you 
will wish to write programs for magazines, for example, and you cannot rely on 
readers having DWBAS. Life will be simpler with DWBAS, but there will still be 
times when you have to use the conventional sequences.

So,  let  me get rid of a couple of bees in my bonnet. I hate the  people  who 

        esc$ = CHR$ (27)

or even worse:

        escape$ = CHR$ (27)

The idea is, presumably, to shorten things, so WHY NOT

        e$ = CHR$ (27) ?

It  also irritates me greatly to see the PRINT AT sequence used without a  DEF 
FN, and the horrible CHR$(32+r) appearing all over a program. If you must  use 
escape sequences, my advice is to write a little subprogram including all  the 
regular ones, and start each new program with it. Unused escape sequences  can 
be deleted when the program is completed. (End of burst of bad temper!)

It is worth studying pages 140-141 (in my copy) of Manual 1 to note the  rarer 
escape  sequences. Some knowledge of the ones to use with the printer is  also 
advisable.  The effect of escape sequences is, of course, quite  different  in 
PRINT statements from that in LPRINT statements.


Many  programs will contain sections that offer the user a choice of  printing 
on  the screen, or sending the same text to the printer for hard  copy.  Quite 
often,  these  program  sections are fairly long, and it seems  to  be  wasted 
effort  when the whole thing has to be written out again with all  the  PRINTs 
changed  to LPRINTs. Of course, it is possible to save some of the  burden  by 
suitable use of AUTO and RENUM, but there is a much easier way.

The idea is very simple. We temporarily send all PRINT commands to the address 
for  LPRINT commands. When we have done the LPRINTing, we change  the  address 
back to normal. POKE 18527,90 changes PRINT to LPRINT. POKE 18527,100  reverts 
to  normal.  On the Amstrad PCW9512, a different version of  Mallard-80  BASIC 
(Version 1.39, as opposed to Version 1.29) is provided, and the addresses  are 
different. The corresponding POKEs are POKE 18591,0 and POKE 18591,10. If  you 
are  writing  for users who might be in either version of Mallard  BASIC,  you 
will have to test. The address I use (out of many possibles) is 5103. If  PEEK 
(5103) = 197, then you are using Mallard-80 BASIC Version 1.39.

There is also a simple POKE to make the PRINTing go to the screen and then  to 
the  printer. POKE 8792,205 in Version 1.29 and POKE 29161,205 in  Ver.  1.39. 
This  make  a machine code jump into a call (like a GOSUB): when it  does  the 
PRINT routine it returns, and where has it got to? The LPRINT routine!  Making 
this change is rather more comprehensive than the previous POKE. Not only does 
PRINT get echoed, but also everything else that goes to the screen,  including 
the  "Ok" prompt! To go back to normal, you POKE 8792,195 with Ver. 1.29,  and 
POKE 29161,195 with Ver. 1.39.

An  omission in Mallard is that there is no official way to TYPE or DISPLAY  a 
file to hard copy. Byt try this in Mallard 1.29:

        POKE 8793, 234 : DISPLAY "FILENAME.TYP" : POKE 8793, 239

Change  8793 to 29162 with Ver. 1.39. If you run an Amstrad PCW9512, you  will 
probably  be using a version of CP/M Plus named 'J21CPM3.EMS' (found  on  your 
Amstrad  master  disc).  In  this case, you should replace  the  234  and  239 
mentioned above with 246 and 251, respectively.

2.3 Randomizing and the clock

You  may  think  that this is an odd pair to take together, but  I  shall  not 
discuss the Amstrad PCW clock at any other point. Many moons ago, I  suspected 
that  a  clock  must  exist, and found it without  too  much  effort.  When  I 
published this result, it was received with interest... but I don't think that 
the  discovery should have been beyond any competent Mallard BASIC  programmer 
prepared  to PEEK and POKE around a little. Everybody knows about the  Amstrad 
PCW clock these days -- but just in case...

The  clock is contained in the bytes 64502-64504, in the CP/M Plus area.  When 
the  computer  is switched on, they are set to 0, 0, and -- guess what  --  0! 
They  represent hours, minutes, and seconds. The clock is  constantly  updated 
through  the interrupt system, and PEEKing these bytes will give you the  time 
since  you  switched  on  the computer. Er, well, it  will  if  you  think  in 
hexadecimal! If PEEk (64504) gives 37, it actually means 25 seconds, as 37 = 2 
* 16 + 5. I can sense some of you writhing. Writhe no more.

For  reading  the  clock: DEF FN a (x) = INT (x / 16) * 10 +  x  MOD  16.  For 
setting the clock: DEF FN b (x) = INT (x / 10) * 16 + x MOD 10. Then, to read: 
h  = FN a (PEEK (64502)) : m = FN a (PEEK (64503)) : s = FN a (PEEK  (64504)). 
To set: POKE 64502, FN b (h) : POKE 64503, FN b (m) : POKE 64504, FN b (s). It 
should be obvious what the variables h, m, and s represent...

You can amuse yourself by finding other ways of doing this, by using HEX$.

Mallard  is not one of the easiest BASICs to use when you need a  sequence  of 
random  numbers. Perhaps random sequences are used mainly for games, but  they 
can  also  serve  serious purposes, such as statistical  simulations.  If  you 
simply put RND in your program at various points, the same sequence of  random 
numbers  will appear every time. Each time you play your  murder  adventure... 
the  butler did it! So, to be clever, you put a line like RANDOMIZE 23 at  the 
start  of your program. This time, the vicar dunnit. But the vicar will go  on 
doing it every time.

What  is needed is a different sequence of random numbers each time.  This  is 
where  the clock can be useful. You 'seed' the random number generator with  a 
number that is itself (approximately) random:

        RANDOMIZE PEEK (64504)

The sequence now will be one of sixty possibles, depending on the exact number 
of  seconds  showing  on  the  clock when  the  game  starts.  For  a  serious 
application,  60 sequences may be insufficient. You could use: RANDOMIZE  PEEK 
(64504) + 256 * PEEK (64503).

An  alternative  approach is to use the same sequence of random  numbers,  but 
enter the sequence at a different point. Quite easy...

        20 PRINT "Press any key to start" : z$ = INKEY$ : z$ = "" : WHILE z$ = 
"" : r = RND : z$ = INKEY$ : WEND

(ROCHE> Indented, this gives:

        20 PRINT "Press any key to start"
        30 z$ = INKEY$
        40 z$ = ""
        50 WHILE z$ = ""
        60       r = RND
        70       z$ = INKEY$
        80 WEND

For  a  game, either approach is adequate. For a more serious  application,  I 
would recommend using BOTH.

2.4 Only 31K?

>From time to time, a letter is published in one of the Amstrad PCW  magazines, 
bemoaning  the fact that, while the writer has a 512K computer, there is  only 
31K  left for programs. I would have a little bet that none of  those  writers 
has,  actually, used more than 5K in a Mallard BASIC program. When  I  started 
programming, I was PLEASED when my programs were so long that this much memory 
had been used!

The reason for the 31K limit is that, in any one microsecond, the Amstrad  PCW 
can only access 64K of memory. A single machine code command can replace  part 
of  this 64K with some of the other memory in the computer. The  principle  of 
Mallard  BASIC  is to have the program and the interpreter in  the  blocks  of 
memory  that  are normally the ones accessed. There is another 80K  of  memory 
that Mallard BASIC (or CP/M Plus) uses to operate the keyboard, screen, discs, 
printer,  and other things. This memory is not entirely sacrosanct,  but  most 
Mallard BASIC programmers will be quite happy to let the interpreter use  this 
memory  'behind  closed doors'. The rest of the memory is the  M  disc.  Don't 
worry  if you think this is too brief to be comprehensible. We shall  go  into 
the memory mapping in much greater detail in Part 2.

When  you are told that you have 31597 bytes left, this refers to  the  memory 
being  currently accessed. If you include the M disc, you have 399K of  memory 
available  for your program on the Amstrad PCW8512 or PCW9512! If that is  not 
enough, you can make use of as many physical discs as you like in Drives A and 
B.  You  are not short of memory -- you just have to manoeuvre it.  There  are 
sufficient  commands  in  Mallard BASIC to make this  perfectly  easy  without 
having to understand how the hidden memory works.

Let  us  suppose  that  you write a program that has a  main  menu  with  four 
options. Each of these options will take about 20K, so that you have a program 
of  nearly  three times the official memory size. You write a  little  program 
called  MAIN. This gives you the main and some CHAIN MERGE commands. The  line 
numbering  might  be below 1000. You then write your four  programs  SECTION1, 
SECTION2,  etc, each starting at line 1000. After your main menu and a  "Press 
key 1-4", you can go to any section in a single line such as:

        CHAIN MERGE "m:" + "SECTION" + CHR$ (z + 48), 1000, ALL, DELETE-999

That's all you need to do to treble your memory!

You  will  notice  that  the section files are on the  M  disc.  This  is  not 
essential,  but  usually  it will make for smoother  operation  if  the  extra 
programs  and any other required files on the program disc are transferred  to 
the  M  disc  before the main menu appears for the  first  time  (Section  4.3 
describes  how to do this). For those of you unfamiliar with the  CHAIN  MERGE 
command  --  I must admit that I usually have to look it up, to check  on  the 
order of the arguments -- the first argument is the file name, the second  the 
line number for restarting, the third says retain ALL variables, and the  last 
gives  the lines to delete from the program already in memory. An  alternative 
to  ALL is to omit this and define some variables (the date, for instance)  as 

A long program will probably contain sequential, random, or Jetsam files. This 
is  another  memory  saving  device, since only the  part  of  the  file  that 
immediately concerns you will be in accessible memory. Such files can be  used 
in unconventional ways, such as containing a machine code routine, as well  as 
the more normal database material.

Some  programs may need pages of instructions. While these can be produced  in 
the  form of PRINT statements in your program, you will probably find that  to 
write them in LocoScript and convert to page image files saves both labour and 
memory. You can then use TYPE or DISPLAY in your program when needed. For more 
advanced programmers, it is also possible to use the M disc as extra memory.

31K? No problem!

2.5 Integer variables, etc

Do  you  use integer variables whenever you can? Probably you should  do,  and 
probably  you  don't  --  like me. It isn't  vitally  important,  but  integer 
variables make your program run slightly more quickly and, debatably, use less 
space. You don't have to put % after them, you can DEFINT a-f for example. The 
former  method  makes for typing mistakes, the latter for  occasional  awkward 
debugging problems. It is sensible to use them in random or keyed files  where 
you  have to make a decision whether to use MKS$ or MKUK$. It is essential  to 
use  them  in  some machine code routines (including GSX, if  you  brave  this 
territory).  It is advisable to make a substantial array integer-variable,  if 
possible. DIM a (100) commandeers 400 bytes. DIM a% (100) takes 200 bytes.

Otherwise, it is largely a matter of style. On the first computer I used which 
allowed integer variables, they actually made the program slower and used more 
space!  They have also been responsible for the worst bug I think I have  ever 
made  -- no, I am too embarrassed to tell you about it! Let us just  say  that 
YOU should use them as often as possible if you want ten out of ten for style.

Double   precision  variables  are  probably  not  much  used.   The   Mallard 
implementation  is  not the jewel in Locomotive Software's crown.  It  is  not 
applicable  to functions such as SQR or SIN, and this is probably where it  is 
most  likely  to be needed -- astronomical calculations, for instance.  It  is 
necessary  to  remember  that double precision calculations will  only  be  as 
accurate  as the least accurate part of the expression. a# = 2.13765894567#  * 
EXP  (4#) will produce lots of decimal places, of which about the  last  eight 
are  meaningless.  It is worth mentioning that a FOR-NEXT loop will  not  work 
with  double  precision variables. If you do this inadvertently,  you  may  be 
scratching your head for a long time!

It is possibly worth a mention that you can obtain double precision values  of 
functions, but you have to write your own routines for them:

EXAMPLE: Return b# as the square root of a#:

        b# = 1# : c# = 0 : WHILE c# <> b# : c# = b# : b# = (c# + a# / c#) / 2# 

(ROCHE> Indented, this gives:

        100 b# = 1#
        110 c# = 0
        120 WHILE c# <> b#
        130       c# = b#
        140       b# = (c# + a# / c#) / 2#
        150 WEND
        160 RETURN

2.6 Loops

I been told, you been told, all God's children have been told: never jump into 
a  loop, never jump out of a loop. Obviously, you can never jump into a  loop, 
because,  if  you encounter a NEXT without a FOR, the computer does  not  know 
where  to  go, and cannot do anything else but produce an error. But,  if  you 
read the manual carefully, you will see that it says that it is permissible to 
jump out of a FOR-NEXT or WHILE-WEND loop. I read that too, and I admit that I 
do occasionally jump out of a loop. But I still think that it is bad practice.

        10 FOR n = 1 TO 20
        50     IF x > 1000 THEN 600
        100 NEXT

On the Amstrad PCW, you will get away with this. On most other computers,  you 
will  get  away with it for a time but, if you do it too often, you  will  run 
into an "Out of Memory" error, or something similar. Consider:

        50     IF x > 1000 THEN n = 30 : GOTO 100
        101 IF n > 21 THEN 600

This completes the loop, and will work on any computer, even if it is not  too 
elegant. With many computers, you could write:

        50 IF x > 1000 THEN n = 30 : NEXT : GOTO 600

However,  Mallard BASIC insists on only one NEXT applying to each FOR, and  so 
this  is not accepted. I suppose this is an exchange for the nearly safe  jump 
from a loop.

Am I making an unreasonable mountain out of a molehill? Can I crash by jumping 
out of a loop? Look at this spaghetti horror:

        100 FOR a = 1 TO 10
        110 FOR b = 1 TO 10
        120 PRINT a, b
        130 NEXT
        140 GOTO 170
        149 FOR c = 1 TO 5
        150 NEXT
        160 NEXT : END
        170 GOTO 160

(ROCHE> Indented, this gives:

        100 FOR a = 1 TO 10
        110     FOR b = 1 TO 10
        120         PRINT a, b
        130     NEXT
        140     GOTO 170
        149     FOR c = 1 TO 5  ' Never executed
        150     NEXT
        160 NEXT
        165 END
        170 GOTO 160

Notice that none of the NEXTs indicates which FOR it is ending...)

Try  it and it will 'work'. Now, delete line 149. You crash DESPITE  THE  FACT 
THAT LINES 149 AND 150 ARE NEVER EXECUTED. I grant that you have to be able to 
write exquisitely badly to produce this standard of program, but it does bring 
home the point that it is best to stick to the rules.

Having  used  BASICs without a WHILE-WEND facility, I know that it  is  always 
possible  to use FOR-NEXT all the time. Some people appear to think  that  the 
WHILE-WEND is the more elegant choice when possible. My own practice is to use 
the  type  of  loop that comes naturally. Some people prefer  to  include  the 
variable name after a NEXT. Maybe it does make programs easier to follow,  but 
it is not necessary, and slows down the program marginally.

2.7 Approximations

A hidden trap in any BASIC that allows Floating-Point numbers (decimals to you 
and  me) is that some numbers that APPEAR to be equal are not  EXACTLY  equal. 

        FOR n = 1 TO 2 STEP 0.1

It  may  seem obvious that the loop will be executed 11 times, ending  with  n 
being 2. This may indeed happen, but it is also possible that the loop will be 
only  executed 10 times. A computer works in binary numbers, and 0.1 will  not 
be  an exact binary decimal. When 0.1 is added to 1 ten times, the result  may 
be  exactly  2, just over 2, or just under 2. If you want to ensure  that  the 
loop does execute 11 times, you should write:

        FOR n = 1 TO 2.00001 STEP 0.1

In the same way, if you test two numbers for equality, or test a number to see 
whether  it  is  exactly  a whole number,  you  should  test  for  approximate 
equality, rather than exact equality:

        IF a = INT (a) THEN PRINT "Result is an integer."

should be replaced by:

        IF ABS (a - ROUND (a)) < 0.00001 THEN PRINT "Result is an integer."

Perhaps  these points have little to do with your BASIC style, but  a  program 
that 'nearly always works' can never be called a stylish one!

2.8 AND, OR, XOR

This section can be omitted if you dislike Maths, but to some of you it may be 
helpful for an understanding of the following Section (2.9). AND and OR  sound 
like  everyday words, XOR doesn't! To the computer, AND and OR mean  something 
very different from what these words mean to you and me. Yet, when AND and  OR 
are  used in an IF-THEN, the computer's interpretation will have  exactly  the 
same result as if it had been thinking in everyday English!

You  have  probably  been told that the computer thinks  in  terms  of  binary 
numbers. Binary numbers are explained in the Graphics chapter, but don't worry 
about them for the moment. Here are two binary numbers:

         73 = 01001001
        200 = 11001000

The  computer  can  'operate' on two binary numbers with  AND,  OR,  and  XOR, 
provided  that they are no more complicated than integer variables.  The  word 
'operate' means: take the two numbers and produce a third number from them.  + 
is an operator, so is - or *.

AND,  OR,  and XOR work on the numbers in their BINARY form, looking  at  each 
column of the 0s and 1s in turn. AND gives a result of 1 only if BOTH rows  in 
the column are 1. OR gives a result of 1 if EITHER row is 1, or both rows  are 
1. XOR gives a result of 1 if one or other BUT NOT both the rows are 1. In all 
other cases, the result of the operation is 0. So:

         73 = 01001001
        200 = 11001000

        73 AND 200 = 01001000 =  72
        73  OR 200 = 11001001 = 201
        73 XOR 200 = 10000001 = 129

What  do you notice about the three answers? Also, you might be  persuaded  to 
try XORing 73 and 200 with the XOR answer 129. Interesting? (Note for  boffins 
only: 0, 73, 129, and 200 form a group with respect to XOR.)

In  the  next Section, when we are talking about -1, we are  actually  talking 
about the integer variable -1, which the computer represents as:


2.9 IF...

In  my opinion, more bad programming is caused by excessive use of IF than  by 
the use of GOTO. I will have my beef about that in the section about  ON-GOTO. 
This is not to say that you should always try to avoid IF statements. They are 
frequently the simplest and best way to do things.

Some  stylish though often trivial effects can be used with IF, provided  that 
the  programmer thinks the way the computer does. The program line  x=4  means 
that  the  variable  x becomes 4. The line IF  x=4...  means  something  quite 
different. x=4... is evaluated as a NUMBER: -1 if the statement is true, and 0 
if the statement is false. IF x=4 AND y=5... contains a mathematical operation 
on two NUMBERS, resulting in -1 or 0.

IF p<>0... can be replaced simply by IF p. While the computer always evaluates 
truth as -1, it accepts as truth any value other than 0. For instance, IF  x=4 
AND  y=5...  will be evaluated as -1, provided both conditions  are  true.  IF 
(x=4)*(y=5)...  would be evaluated as +1, but it would still have exactly  the 
same effect. In the same way, '+' can replace OR, and usually '-' can  replace 

Thus, IF p<>0 AND q<>0... can be shortened to IF p*q. When you use a condition 
that mixes AND with OR, it is often awkward to be quite sure what will  happen 
in all cases. Using * and + may clarify things to the programmer.

IF  x=0...  can sometimes be replaced by IF NOT x. You may say  that  this  is 
longer.  Quite!  (Although it is shorter in program storage.)  Yet,  I  cannot 
recall anyone who writes WHILE EOF (1) = 0. Admit it! You write WHILE NOT  EOF 

Occasional programming tricks can be performed by using the words AND, OR, and 
XOR in their mathematical sense. One of the neatest is toggling a flag from  0 
to 1.

        IF x=1 THEN x=0 : ELSE x=1...

can be replaced by

        x = x XOR 1

Chapter 3: Input techniques

INPUT techniques are probably the most boring programming tasks that you  will 
have  to  do. They are also going to be the part of the program by  which  you 
stand  or  fall when you let somebody else use your program. This is  not  the 
most interesting chapter in the book. It may be the most important if you hope 
that your programs will be sold, given away, lent, or pirated.

3.1 INPUT, INKEY$, etc

The  clarity of your prompts is not something that we can demonstrate here  -- 
this  is  not an English grammar textbook -- and the depth of  input  checking 
will  depend  entirely on whether you rate the user at idiot or  moron  level! 
What  we can do is show you efficient ways of programming for the  input  that 
you need.

Some  programs  will  run with keypress responses from the  user.  INKEY$  (or 
INPUT$) will be sufficient. Others require sentences, words, or numbers to  be 
entered.  These programs will need INPUT. Many programs require a  mixture  of 
keypresses  and longer INPUTs. For these, should the programmer use INPUT  for 
all  responses,  or  should  the program use INKEY$ when  possible?  It  is  a 
question that has not been entirely settled. The case for using INPUTs only is 
based on consistency. Up to a point, I agree. A good program is consistent. If 
you use "Press Space Bar to Continue" at one point, it is poor programming  to 
use "Press RETURN to continue" at another. If you use INPUT and INKEY$ in  the 
same  program,  the user can be muddled about whether a press of  [RETURN]  is 

On the other hand, it is also good programming practice to ask the user to  do 
as  little as possible. It seems to be wrong to ask for a [RETURN] press  when 
it is not needed. My own practice is to use INPUT only when needed, and INKEY$ 
otherwise. I do make a point of using the verb ENTER for INPUT, and PRESS  for 
INKEY$ (other words will do equally well, provided that the use is  consistent 
throughout  the program). Certainly, I get mildly annoyed with a program  that 
uses INPUT when INKEY$ would do.

Another  point to consider is whether you should use the facility to  INPUT  a 
numeric variable. For example, you might use:

        60 INPUT "Enter cost price in pence: "; cp : IF cp <= 0 THEN 60

You could also use:

        60  INPUT "Enter cost price in pence: "; cp$ : cp = VAL (cp$) : IF  cp 
<= 0 THEN 60

The  latter  seems  preferable, as you avoid the possibility  of  the  message 
"?Redo  from  start"  which can muddle your palooka, and an entry  of  60p  is 
accepted  correctly.  However,  I don't have strong feelings one  way  or  the 
other,  as  a  great deal depends on the expected skill of the  user.  A  good 
stylist might well add an error message and a beep in these lines, but that is 
not the point, here.

We  have  used  INPUT and INKEY$, since most programmers  seem  to  use  these 
methods  exclusively. In the following section, we shall discuss  alternatives 
to INKEY$. There is also an alternative to INPUT, LINE INPUT. The manual  does 
not make it very clear why anybody should ever want to use LINE INPUT, but the 
main point is that you can include commas (",") in the input (an address,  for 

3.2 INKEY$ or...

All BASICs have a method for scanning the keyboard and putting the result in a 
variable  -- usually, but not always, a string variable. The word used can  be 
INKEY$, INPUT$, GET, or KBD. (GET means something different in Mallard  BASIC, 
and  KBD means nothing at all.) The result of such a scan will also depend  on 
how  the interpreter is programmed to respond to the scan. To  know  precisely 
what is happening, you have to know the answers to two questions:

     1) "Is it a RETROSPECTIVE or INSTANT scan?"

     2)  "Will  it wait (STATIONARY) or continue (MOVING) if  no  keypress  is 

Every  time  that  a line is executed in Mallard  BASIC,  the  interpreter  is 
programmed  to  look at the keyboard. If a key is pressed, it will  be  noted. 
There is a mechanism to prevent this happening if the last effective  keypress 
is  still being held down. (The locations for these signals will be  found  in 
Chapter 14.)

When  an  INKEY$  or an INPUT$ is met later in the  program,  a  RETROSPECTIVE 
keypress  (one that has previously been signalled) will be taken as the  value 
required. At this point, z$ = INKEY$ and z$ = INPUT$ (1) differ. INKEY$  takes 
a last look at the keyboard and carries on with the program (MOVING),  leaving 
z$  =  "" if no response is forthcoming, whereas INPUT$ (1)  rescans  until  a 
positive response is achieved (STATIONARY).

Since  the  usual response required is STATIONARY, INKEY$ is often  used  when 
there  is  a  better way to program. The easiest way to  turn  INKEY$  into  a 
STATIONARY  GET  is  very rarely used! The manuel does not  use  it,  and  the 
natural  tendency of the user is to believe that, if the  manual  demonstrates 
one  method, there cannot be a simpler way. Usually right, but not this  time! 
Instead of:

        z$ = "" : WHILE z$ = "" : z$ = INKEY$ : WEND

(ROCHE> Indented, this gives:

        100 z$ = ""
        110 WHILE z$ = ""
        120       z$ = INKEY$
        130 WEND

Personally, I use WHILE INKEY$ = "" : WEND...)

just use:

        z$ = INPUT$ (1)

INPUT$  (1) will pick up a retrospective keypress, so that, if you don't  want 
this to happen, you can precede it with z$ = INKEY$.

If  you  are  satisfied  with ANY keypress,  an  alternative  method  for  the 
stationary  get is to define v = 1018 and use CALL v every time you request  a 
keypress. This uses a routine in the Mallard BASIC interpreter.

Alternatively,  you  can use the CP/M Plus routine, which has  the  difference 
that a restrospective keypress is not picked up. You can do this by defining v 
= 65062 (65074 if you are using the Amstrad PCW9512), and again CALL v.

>From  this point on, I shall assume that I have converted you to INPUT$! If  I 
have not done so, all comments about INPUT$ will also apply to INKEY$.

3.3 Filename checking

I  will  readily  admit  that  checking INPUT  is  not  one  of  my  favourite 
programming  jobs.  A frequent example is the INPUT of a filename.  You  don't 
mind  if the user calls the database JEAN or SALLY.ANN. But the computer  will 
mind  if  it  is called ESMERALDA or Mrs J.A.SMITH. I will  admit  to  writing 
extensive  checks to find out if a filename entered was acceptable,  but  this 
was  largely  wasted  effort.  It brings in an  important  principle.  If  the 
computer will do it for you, don't do it yourself.

With  an ON ERROR check, I improve things by using FIND$, but this did mean  a 
short delay and a user-unidentifiable whirring. I hit on the idea of  altering 
the  KILL  routine by changing a CALL to a JP (ROCHE> Z-80  mnemonics...),  so 
that the routine would error check without erasing the file. Now, I think that 
the  simplest way is to use FIND$, but to check on the M disc, which  is  much 
quicker, and silent. This sort of thing:

        200 INPUT "Enter filename xxxxxxxxxxx: ", f$
        210 ON ERROR GOTO 5000
        220 g$ = FIND$ ("m:" + f$) : ON ERROR GOTO 0
        230 IF f$ = "" THEN 5010
        240 REM Etc.
        5000 RESUME 5010
        5010  PRINT CHR$ (7) ; "Why don't you do what you were told?"  :  GOTO 

xxxxxxxxxxx  represents  3 pints of Australian beer, or however  you  want  to 
prompt the user about filename choice.

3.4 Date checking

I don't know what it is about dates, but most programmers seem to have gone to 
great  pains  to  devise a routine to input dates, with  numerous  checks  for 
accuracy. So, I am not going to try to slay your own particular sacred cow  at 
this  point. If you have devised such a routine, I am sure that it  is  better 
than anything I can show you!

I  am afraid I simply ask for day, month, and year as separate inputs.  It  is 
really easier all round than asking for input in a six figure form, say 070289 
for 7 February 1989. Section 7.5 gives a routine for friendly month input.

There  are a great many programs that do not need the date, but still  request 
it  and  put it on every print out. Some users like this, but others  will  be 
irritated. An answer to this is to use a 'hidden menu', so that any  keypress-
routine  checks for certain letters which temporarily interrupt  the  program. 
Using [ALT]-D for date entry will not interfere with anything else, and a user 
who  likes to date the documents can press this at any time. Those  who  don't 
want  to do so are not obliged to do so. Another friendly little trick on  the 
hidden  menu is to allow [ALT]-I to invert the screen to black  characters  on 
green background. If you use this as a toggle, with the variable fl signalling 
the  current  condition, fl = fl XOR 1... is a convenient way to  operate  the 

3.5 Checking input

        10 INPUT "Please enter your name: ", a$
        20  PRINT : PRINT "Is your name "; a$; "? Press Y for YES, and  N  for 
        30 z$ = UPPER$ (INPUT$ (1)) : IF z$ = "N" THEN 10 : ELSE IF z$ <>  "Y" 
        40 PRINT : PRINT "Hello, "; a$; "!"

(ROCHE> Rearranged, this gives:

        10 INPUT "Please enter your name: ", a$
        20 PRINT
        30 PRINT "Is your name " a$ " (Y/N) ?"
        40 z$ = UPPER$ (INPUT$ (1))
        50 IF z$ = "N" THEN 10 : ELSE IF z$ <> "Y" THEN 50
        60 PRINT
        70 PRINT "Hello, " a$ "!"

Grrrrrh.  Yes, I know that we have to do this for some programs. We have  also 
run some programs that do this to us, and drive us to distraction! The balance 
between  over-checking  and  under-checking is a delicate one,  and  only  the 
programmer can decide what is right.

Let us suppose that we are writing a program which asks for entries of a share 
purchase.  Perhaps  10  items of information will be needed. You  or  I  could 
easily  make a mistake in entering this much, so some element of  checking  is 
needed.  Checking ech item as entered is one solution. Giving an option to  go 
back and start again after the 10 items are entered is another solution.

Neither is very satisfactory, but what about this?

     1. Clear the screen.

     2.  Print  an  instruction  at the top. PRINT  (not  INPUT  yet)  all  10 
questions with any prompts on alternate lines.

     3. Use the CHR$ (27) "Y" sequence (or an easier replacement) to move  the 
cursor to the answer to each question in turn, and accept the INPUT.

     4. Ask whether to proceed. If the answer is no, then go back to the INPUT 
sequence,  but  this time accept a simple [RETURN] as  confirmation  that  the 
entry stands.

I  don't  think  that you will be able to write this routine in  a  couple  of 
minutes,  but, by using an array for the questions and another array  for  the 
answers,  and sensible loops and subroutines, you should be able to develop  a 
routine  that,  once  written, can easily be adapted to  a  similar  situation 
elsewhere  in  the program or, indeed, in another program.  The  second  input 
sequence  will  probably  need  INPUT$ (1), INPUT, and SPACE$  to  work  in  a 
presentable manner.

An  interesting  little  program to try along these lines  is  to  prompt  for 
entries  of Cost Price, Selling Price, and Profit %. A simple [RETURN] at  the 
INPUT is taken as 'Don't know'. When two inputs have been made, the third  one 
is filled in. You have made a mini-spreadsheet! Obviously, this idea could  be 
extended to a greater number of variables.

In  Appendix  7, we use the multiple input and spreadsheet idea in  a  program 
using  DWBAS. In elementary mechanics, there are simple relationships  between 
initial  velocity, final velocity, acceleration, distance, and time  (assuming 
the acceleration is constant). Any three of these determine the other two.  If 
you  study  this program, you will see that it accepts any three of  the  five 
inputs you can make, and produces the result for the unknown inputs.

3.6 ON z GOTO or GOSUB

Some programmers do not appear to have heard of the word ON. This ought not to 
be  the  case.  ON is a word that EVERY Mallard BASIC  programmer  should  use 
often. The worst examples of not using it come in games programs, and you  can 
generally  spot  a bad programmer by a set of five to ten lines  all  starting 
with the word IF. I have sometimes (out of the goodness of my heart) rewritten 
such  programs,  and  the authors have been amazed at how their  pigs  fly  at 
double  the speed. The reason for this is that the original program  may  have 
had  to evaluate and make 10 different decisions, of which only one  could  be 
true.  Using ON, all the decisions are made in a single statement. But  it  is 
not  only in games programs that ON is conspicuous by its absence, so  let  us 
consider the use after a menu choice has been given.

It  is  simpler for the programmer, and not usually a great  hardship  to  the 
user,  to  write  a  menu that ends with a  prompt:  "Please  press  key  1-7, 
depending on choice." In this case:

        150 z$ = INPUT$ (1) : z = ASC (z$) - 48 : IF z < 1 or z > 7 THEN 150

The variable z contains 1-7, and is immediately ready for ON z.

You  cannot use this if there are 10 or more options. In that case, you  could 
use  the  letters  A-M (say) to identify the chosen option.  Here,  you  could 
calculate z by: z = ASC (UPPER$ (z$)) - 64 or the slightly fancier:

        z = (ASC (z$) AND 223) - 64

I  know  what you are going to say next. Your accounts program is  written  so 
that the menu offers C for Cashbook, P for Payroll, I for Invoices, and Q  for 
Quit, and you don't want to change them to 1-4 or A-D. Fair enough. There is a 
simple solution using INSTR:

        150 i$ = "CPIQ" : z$ = UPPER$ (INPUT$ (1)) : i = INSTR (i$, z$) : IF i 
= 0 THEN 150

A point worth making for the games programmers is that this technique can also 
be used with the cursor keys:

        i$ = CHR$ (1) + CHR$ (6) + CHR$ (31) + CHR$ (30)

will give you left, right, up, and down.

Menus  are far from being the only place where ON makes for  neat  programming 
but,  if  you make a point of using it in this fairly obvious  situation,  you 
will  come  to  realise  the  many other occasions when  you  can  use  ON  to 

You  can either use ON GOTO or ON GOSUB. Which is best depends on the  general 
structure of your program. Don't feel that, because people say that GOSUBs are 
more  stylish than GOTOs, you should try to use ON GOSUB rather than ON  GOTO. 
Use the version which is more natural.

3.7 Menus

In the old days, programs were advertised as Menu Driven. The reader was meant 
to think: "Wow, how clever!" and reach for the cheque book. If you think about 
it,  there  are  many programs which would be  extremely  difficult  to  write 
without the use of Menus. Consider this approach:

        100 DATA Rates and Rent, Food, Clothes, Drink, Car, Fuel, Holidays
        101 Data Miscellaneous, Quit
        110 t$ = " M A I N M E N U " : j = 9
        120 RESTORE 100 : FOR n = 1 TO j : READ a$ (n) : NEXT
        130 GOSUB 5500
        140 ON z GOTO...

Since  the Menu idea is really so simple, you will find that most  programmers 
do  not  give  it  sufficient  thought. What  I  have  written  above  is  the 
information for a particular menu. What the subroutine called at 5500 will  do 
is to print and operate the menu. Have you seen the point, yet? The subroutine 
at 5500 will be able to operate ANY menu if you give it the information as  we 
done above.

How you write the subroutine at 5500 is up to you. You can spend some time  on 
it, as you will not have to rewrite it in your program, however many menus you 
evoke. Indeed, you will be able to pirate the code for any other programs that 
you  write that need menus. (Appendix 6 gives a Menu subroutine in DWBAS  that 
you may or may not wish to use.)

Perhaps  you  will centre the title string, call the items 1 to 9, and  use  a 
loop  for printing on alternate lines. Notice how simple the use of  an  array 
makes  the  coding. You will prompt for your keypress, and write  code  for  a 
subroutine  to  obtain  the variable z as 1 to 9. If you like,  you  can  make 
things more decorative by suitable use of inverse video and a box design.  You 
will use the variable j at various points in the subroutine, so that the  code 
will adapt itself to different numbers of items on the menu. And you will have 
a  menu-sub  that will last for life. OK, you say, that's such a  simple  idea 
that  I could have thought of it myself, and you have been wasting good  paper 
telling  me. Yes, you could. But I didn't -- until I had written  perhaps  100 

Chapter 4: Files

In  this  chapter, we look at BASIC problems that will only occur if  you  are 
using the discs to read or write files. Mallard BASIC is clearly written  with 
such programs very much in mind, and works very efficiently. The manual covers 
the various types of files available in Mallard BASIC more thoroughly than  it 
covers  other  aspects of programming. I have not a lot to add to  this.  Your 
efficiency  with  files will depend more on your general  programming  ability 
than anything I say here. However, there are one or two little points that are 
specific to such programs.

4.1 Sequential, random, or Jetsam?

Should  you use sequential, random, or Jetsam files? I have used all  of  them 
but, generally, a simple random file seems to suit most purposes best.  That's 
got  a few Jetsam fans jumping up and down in their seats with anger! My  view 
of  Jetsam  is that it is a very fine solution for some  programs,  especially 
very  large  databases,  but  that, most of the time,  it  is  an  unnecessary 
elaboration of what could be done with little loss of speed, and some gain  of 
space,  by random files. If I am wrong, I bow to your superior  knowledge.  My 
only  excuse  is  that  I don't enjoy writing  database  programs  very  much, 
although one has to do so, at times.

Are there any programming tricks that I can pass on from experience? Not many. 
If you follow the manual and the example programs, you should be able to write 
at least as good a filing program as I can.

EOF is a useful tool to signal the End Of File. In a sequential file, it works 
well. For example, we might use

        PRINT "Teams in Football League Division 1:" : OPEN "I", 1, "div1.lst" 
: WHILE NOT EOF (1) : INPUT# 1, a$ : PRINT a$ : WEND : CLOSE

(ROCHE> Indented, this gives:

        PRINT "Teams in Football League Division 1:"
        OPEN "I", 1, "div1.lst"
        WHILE NOT EOF (1)
              INPUT# 1, a$
              PRINT a$

However,  if the list of teams had been stored in a random file,  this  simple 
program  does  not work (even if INPUT# is replaced by GET). EOF  with  random 
files has caused me (and I believe others) some headaches. An EOF is signalled 
at  the  start  of a random file, as well as the end. This  might  be  a  good 

        WHILE LOC (1) = 0 OR EOF (1) = 0 :...: WEND

There is also a problem when you are GETting and PUTting in the same routine:

        WHILE LOC (1) = 0 OR EOF (1) = 0 : n = n + 1 : GET 1, n :...: PUT 1, n 

The  PUT makes the EOF meaningless. One solution is to insert a dummy GET  1,n 
between the PUT and the WEND. Inelegant, but it works.

MKS$ and CVS (among others) are mystery words to some programmers. The  manual 
uses  them, and the manual, like the laws of the Medes and the Persians,  must 
be  obeyed.  All MKS$ actually does is to code any single  precision  variable 
into  4 bytes, which will usually have the effect of compressing the code.  If 
you  prefer,  you  can enter a number as a string and  subsequently  use  VAL. 
Incidentally, MKS$ (a) will contain the same four bytes as you would read from 
VARPTR (a) (See Section 7.3.). More mundane than mystical!

Another problem occurs when it is necessary to manipulate a string defined  as 
a  field.  If you have written FIELD 1,128 AS a$, don't alter a$  itself,  use 
another variable:

        GET 1, n : b$ = a$ :...: LSET a$ = b$ : PUT 1, n

You  are  allowed to use #1 instead of 1 as a file-reference number.  Why  you 
should  want to do so (except that, at one point in the manual, this is  used) 
is a mystery. Some people do use # in this context. I know because it does not 
work in an extended BASIC that I have written. Please, omit the #!

4.2 Saving memory

Most people think that a sequential file is the simplest type of file to  use, 
and  that a random file is more sophisticated. The reverse is  probably  true, 
especially in the case where there is just one field, normally 128 bytes long. 
Such a file can be used for saving ANYTHING in 128-byte blocks. In particular, 
a random file can be used for saving blocks of the computer memory, instead of 
the  strings of DATA which are usually the building blocks of such a  file.  A 
COM  file,  which is a machine code program which loads at  &H0100  (256),  is 
identical  to  a random file of 128-character strings, each character  in  the 
string corresponding to one byte of machine code.

This use of random files is probably not generally known, and is not mentioned 
in  the  manual, so we will include a tutorial program. We are going  to  save 
Mallard  BASIC  but, before we save it, we could make  some  alterations.  For 
example, if you prefer the different operation of REM, as suggested in Section 
6.1, you could load the normal Mallard BASIC, do the relevant POKEs, and  save 
BAS2 using this program. BAS2 can then be loaded on a future occasion, instead 
of Mallard BASIC.

A recommended alternative amendment is to POKE 257,150 and POKE 258,1 to  save 
a  'warm  start'  Mallard  BASIC.  The value of  this  will  be  seen  if  you 
inadvertently crash into CP/M Plus and want to recover not only Mallard BASIC, 
but  your unsaved program (See Section 5.5.). A normal load of  Mallard  BASIC 
does some housekeeping, which includes clearing out any program and  variables 
in  memory, and giving the start up messages. If this routine is  bypassed  by 
the POKEs above, you save any Mallard BASIC program already in memory --  this 
is called a warm start. You cannot use a warm start Mallard BASIC if you  have 
just switched on the computer.

If  you  use  the  program  below, load Mallard  BASIC  but  DO  NOT  add  any 
extensions.  Just do the required POKEs, and load and run the  program  below. 
Obviously, you will need 28K spare on the disc.

        10 b$ = SPACE$ (128) : a = 1 : b = 0
        20 OPEN "r", 1, "bas2.com", 128 : FIELD 1, 128 AS a$
        30 FOR n = 0 TO 223
        40  v = VARPTR (b$) : POKE v + 1, b : POKE v + 2, a : LSET a$ =  b$  : 
        50 a = a + b / 128 : b = b XOR 128 : NEXT : CLOSE

(ROCHE> Indented, this gives:

        10 b$ = SPACE$ (128)
        20 a = 1
        30 b = 0
        40 OPEN "r", 1, "bas2.com", 128
        50 FIELD 1, 128 AS a$
        60 FOR n = 0 TO 223
        70     v = VARPTR (b$)
        80     POKE v + 1, b
        90     POKE v + 2, a
        100    LSET a$ = b$
        110    PUT 1
        120    a = a + b / 128
        130    b = b XOR 128
        140 NEXT
        150 CLOSE

Line  40 is well worth a close look. What we are doing is a con trick  on  the 
computer  to  make  it think that b$ is stored in the  relevant  part  of  the 
Mallard BASIC interpreter. It is then LSET from there into the respectable a$, 
and the computer does not even know that it has been conned. The use of XOR in 
line 50 is also a point to note.

A friend commented that he found this program very difficult to understand (he 
is  being unduly modest). Il will agree that there are some unusual  concepts, 
but the idea may well become clearer when you have read the comments on VARPTR 
in  Chapter  7. It may also be instructive to include these two lines  in  the 

        45 PRINT a * 256 + b
        46 PRINT b$

These give the address of the start of the record that is being processed, and 
the  contents of the string being entered in the file. On the first loop,  for 
example,  you  will  see that it contains most of the  start  up  message  for 
Mallard BASIC.

If  you save memory in this way, it is not essential that the  locations  from 
which it was saved are the same as the locations to which it will be reloaded. 
Hence, a COM file can be written in high memory, or even in strings saved to a 
random file, with the Mallard BASIC interpreter loaded.

4.3 File transfers

If  you wish to transfer a random file from one disc to another in  a  Mallard 
BASIC program, say ADDRESS.DAT from drive A to M, this method will work:

        100 OPEN "r", 1, "address.dat" : FIELD 1, 128 AS a$
        110 OPEN "r", 2, "address.dat" : FIELD 2, 128 AS b$
        120 WHILE EOF (1) = 0 OR LOC (1) = 0
        130       GET 1 : LSET b$ = a$ : PUT 2
        140 WEND : CLOSE

Oddly enough, the above method will work even if the file is a sequential one! 
While  the  program runs, the computer is fooled into thinking that  it  is  a 
random file.

Chapter 5: Editing and debugging

Programming  is done by a consenting adult and computer in private, so  it  is 
not  always  easy  to find out what other programmers  actually  do  in  these 
sessions.  Much of my time, and probably much of your time, is  spent  writing 
programs.  It  is  obviously  desirable  that this  time  should  be  used  as 
efficiently as possible. Experience is probably the greatest teacher, in  this 
respect. We discover tricks, we file them in our own memories, and use them on 
future  occasions.  Yet, I have often thought: "Why didn't I  realise  that  I 
could do that six months ago? It would have saved so much time!"

It is by no means a bad idea to sit behind another programmer and watch how he 
or  she  does  it. For example, I watched somebody use the  PIP  command,  and 
suddenly realised that I had been using it thoroughly inefficiently.

In one sense, this is not the easiest of chapters to write. Some things become 
so automatic that it is hard to realise others people may not know the tricks. 
You  will  have  to forgive me when I mention the obvious --  it  may  not  be 
obvious to somebody else.

Editing, debugging, and avoiding pitfalls due to oddities in the  interpreter, 
are all part of the same process -- transferring an idea into code that works. 
We include all these aspects in a single chapter.

5.1 Editing

Most  manuals seem to assume that the programmer is nearly perfect,  and  that 
most  of  the  programs  will run first time. Very  little  is  written  about 
editing,  testing, and debugging programs. Most programmers spend at least  as 
long  on  this stage as on writing the programs. We all have  our  methods  of 
sorting  out  the problems, and nobody criticises us because it  is  all  done 
behind  closed doors. My baptism of fire was to teach a class of pupils  in  a 
room full of computers, with a dozen different programs going wrong in a dozen 
different places... That soon made me learn speedy techniques!

The  Mallard BASIC editing system is not the friendliest that I have met,  but 
it is not nearly as bad as I thought it was when I first used the Amstrad PCW. 
For  instance,  it is fussy about spaces, and friendly about whether  you  put 
words in capital or small letters. Previously, I had used computers that  were 
fussy  about capitals and tolerant about spaces. It is natural to  prefer  the 
system  that you know, and any strange computer will seem difficult,  in  this 

One  of  my  initial  grudges was quite unfair. I do  a  lot  of  testing  and 
discovery  work  in  direct  mode -- writing a  single  unnumbered  lined  and 
pressing [RETURN]. One that I have written countless times is a variation on:

        FOR n = 1 TO LEN (b$) : PRINT ASC (MID$ (b$, n)); : NEXT

It  is  at  least even money that I miss out a semicolon  or  a  bracket  and, 
without  a  full  screen  editor, you have to go  through  the  whole  process 
again... and so on. Or do you? The manual tells you that you can press [ALT]-A 
to  recapture your line. Muggins did not read that for two months. Two  months 
later,  I realised that you DON'T have to press [ALT] and A -- cursor left  is 
simpler!  The manual does not tell you that, unless you have the correct  page 
in Manual 1 and the correct page in Manual 2 open at the same time!

Cursor left has uses other that in direct mode. Pressing [RETURN] or [STOP] by 
mistake when you are writing a long line is annoying, but pressing cursor left 
immediately  presents it again. Did you write LIST 40 when you meant EDIT  40? 
Press  cursor left, LIST a program, and follow with cursor left. You can  edit 
the  last line. You have written SAVE"MYPROG", and it is such a  good  program 
that  you  want it on the backup disc. Change discs, press  cursor  left,  and 
[RETURN]. A curiosity here is that this does not work with SAVE"MYPROG",A. The 
translation from the semi-compiled version of a Mallard BASIC program back  to 
ASCII uses the vital buffer.

There  are  two other controls that I use regularly.  Pressing  [CUT][CUT]  (a 
double press of the [CUT] key) will erase the rest of the line from the cursor 
position. Pressing [FIND][FIND] takes the cursor to the end of the line.  Some 
people  may  use the [CUT] and [FIND] keys with other arguments.  [FIND],  for 
instance,  would take you to the next statement. It may be obvious, but  using 
cursor  down  can save time in editing a long Mallard BASIC  line.  Some  very 
nimble-fingered  people might find it an advantage to speed up the repeat  key 
rates -- it is possible to do this.

It  is  debatable  whether it is advantageous to set other  function  keys  to 
assist editing. This can be done through SETKEYS, or directly in the Lightning 
BASIC extension (See also Chapter 16.). It may save time to set, say, [f8]  to 
SAVE"MYPROG" and [f7] to LIST 1000-1200. Usually, I feel it is not quite worth 
the bother, but it depends on the individual's programming methods.

You  probably know that [f5] can be used to halt a listing or a  program,  and 
any  further  keypress restarts. You probably know that ? can be  used  as  an 
abbreviation  for PRINT, and that you do not have to follow it with  a  space. 
But, maybe you didn't know!

AUTO  and  RENUM  are useful editing tools, but we shall  deal  with  them  in 
Section 5.3.

5.2 Debugging

Debugging takes more hours out of programmers' lives than you would believe by 
hearing  them talk of their successes! You may feel that you are the only  one 
who  has  to  write and rewrite and rewrite again. Believe me,  you  are  not. 
Generally,  I  make  more mistakes when I write in BASIC than  in  code.  Most 
Mallard  BASIC  errors are simple to sort out, and a program crash is  only  a 
minor  setback.  If you crash in code, you usually have to start up  all  over 
again, and so it is worth checking things very carefully before putting it  to 
the test by running. In Mallard BASIC, it is not usually worth the bother. The 
golden  rule  is  to SAVE before you test. There are countless  times  I  have 
regretted  my carelessness! The second golden rule is to write short  sections 
of a program, and test each section before you continue.

Syntax errors are usually the simplest. The report: "Syntax Error in line 567" 
will  mean, 99 times out of 100, that there is something wrong with line  567. 
Look  at  it. Os and 0s can cause problems, as can Is and 1s.  A  colon  (":") 
instead  of a semicolon (";") in a PRINT statement may be the culprit. If  the 
mistake  is not obvious, count the opening brackets and closing brackets,  and 
see if they balance. If the line is long, test to see how far it did  actually 
execute -- is the sixth of ten statements is v=20, use ?v to see if it  really 
is 20.

Having  said that, I have recently spent some time debugging  somebody  else's 
program  that crashed with "Syntax Error in 1031". The error turned out to  be 
not  even  in  the program itself, but in a previously  chained  program!  The 
hardest  errors to correct are those which occur because of a previous  error, 
and  not  on  the line itself. A common one is that a DEF  FN  is  incorrectly 
entered,  but the error occurs much later, when the function is  used.  DEFINT 
can cause problems that are tough to identify.

It is worth mentioning that, if you want to test part of a program, using GOTO 
(or  GOSUB) instead of RUN retains the variables. The only official  debugging 
aid that you have with Mallard is TRON. If I have a TRON, I don't often use it 
--  if I haven't got one, I always seem to need it! Debugging tools are  often 
conspicuous  by  their  absence, and Lightning BASIC does  include  a  program 
search,  a variable list, a variable-speed TRON, and the error line  presented 
for  editing for most errors, instead of just for Syntax Error.  These  things 
all help, but your main debugging aids will be intelligence and experience.

There is another type of debugging that is worth a mention. If you are writing 
a  lot  of PRINT lines for instructions, or designing a pretty menu  or  title 
page, don't expect to get it right first time. Write it quickly, and don't try 
to  do more than get the syntax and most of the spelling right. Run it and  it 
will be much clearer from the screen what alterations should be made.

We all have programs that just will not come right. The quickest answer can be 
to  go back and start again, but I cannot say that I like doing this.  If  you 
do,  you  will  never know what was wrong, and  many  programming  skills  are 
developed from one's own mistakes. Not so long ago, I had spent four hours  on 
a  section of a program that just would not work right. In disgust,  I  turned 
off the computer and went out. Half way down the third pint, I knew in a flash 
exactly what was wrong and, when I came home, it only took a couple of minutes 
to have the program working perfectly. If you are teetotal, sleeping on it  is 
a good alternative!

I may be in line for the Masochist of the Year competition as I find debugging 
quite  a  satisfying part of programming! Often, I  enjoy  debugging  programs 
written  by  others. Sometimes, this needs a special sort of  tact,  when  the 
programs  are  so bad that it would be quicker to scrap everything  and  start 
from  scratch! But it brings me to perhaps the most important point of all  in 
debugging. Somebody else is much more likely to see the glaring mistakes,  the 
muddled instructions, the input checks needed, the short cuts you have missed. 
If  you  are  at all serious about a program, pass it on to  a  friend  before 
letting  it  loose  on the general population. With the  Amstrad  PCW,  it  is 
helpful  if  your friend has a different model of the machine, to  check  (for 
example) that something written using CP/M Plus Version 1.4 and Mallard  BASIC 
Version 1.29 will actually run on CP/M Plus 2.1 and Mallard BASIC 1.39.

5.3 AUTO and RENUM

It  is  debatable whether AUTO is more of a help or a hindrance when  you  are 
composing  a  program. If you are copying from paper, of if the code  is  very 
straightforward,  it  can  be of assistance. If the program is  likely  to  be 
difficult and to need alterations as you write it, it is probably best not  to 
use AUTO.

The real value of AUTO is in debugging. AUTO 3000,10 will present each line of 
your  section,  starting  at 3000, in turn. You just press  [RETURN]  for  the 
satisfactory  lines,  and  edit those that need alteration. If  you  have  not 
numbered  in  10s, it may be necessary to use AUTO 3000,1  and  keep  pressing 
[RETURN]  for empty lines. Even this is usually much quicker than  using  EDIT 
for each alteration.

Consider  this  frequent situation. You have code at  3000-3400  which  prints 
pages of information to the screen. You want to allow a hard copy print of the 
same  information. There will be some differences in the format of  the  paper 
printing,  so  the POKE suggested in Section 2.2 is  not  appropriate.  First, 
SAVE"PROG"  if you haven't done so. DELETE-2999 and DELETE 3401-.  Now,  RENUM 
4000.  AUTO  4000, and make the changes as the PRINT  lines  appear.  Finally, 
MERGE"PROG".  All very simple, but the sort of thing that  programmers  easily 
miss. An alternative way of making a 'near-copy' of a section of a program  is 
to  call  it as a flagged subroutine, so that the flag gives  the  alternative 
actions when they occur. You can get back with:

        IF sr = 1 THEN sr = 0 : RETURN

I am not entirely convinced about the merits of RENUMbering a program when  it 
is  complete. It may appear to be more organised, but it may also  prove  less 
readable  after  the  RENUM. It is quite likely that you  used  a  pattern  of 
numbering when you wrote the program, and this pattern should become clear  to 
the  reader,  and in effect be a pseudo-index. There is certainly a  case  for 
RENUMbering each subroutine that you write before incorporating it in the main 
design.  If  you  are writing for a magazine that expects  users  to  type  in 
programs,  it is usually sensible to RENUMber. This allows the typist  to  use 

5.4 Subroutine libraries?

Many programming tasks are repetitive, so it makes a lot of sense to create  a 
subroutine library. It also takes more organisation than I usually display, so 
I  cannot honestly say that I have a library of my own. However, I do  have  a 
good  memory, and often lift subroutines from one program to another.  I  also 
have  programs with specialist types of subroutines, such as graphics. Yet,  I 
have  to admit that I do reinvent the wheel from time to time -- it  is  often 
quicker  to  rewrite  a routine than to find it somewhere  else.  It  is  also 
possible that rewriting a subroutine improves on the original.

If  you  do  write a subroutine library, it is a good  idea  to  use  internal 
variables  (ones that are NOT passed to the subroutine by the  main  program), 
with  names  you are not likely to use elsewhere. For example, you  could  end 
variables with a 9, so that a subroutine might start with h9 = HIMEM.  Library 
(or  library-type)  subroutines should be as general  as  possible.  Variables 
should  not  be  assumed to have a value of 0. Code routines  should  be  made 
relocatable if possible, and should depend only on the initial value of HIMEM. 
If you use DATA, you should include a RESTORE.

On the whole, a subroutine library is a good idea, if you forgive the 'Do as I 
say, don't do as I do' attitude.

5.5 Mallard 'bugs'

One  aspect  of  the  programmers task is  to  make  allowances  for  possible 
weaknesses  in the language used. Luckily, Mallard scores very high  marks  in 
this respect -- I mean it has few bugs, not many! In fact, I don't think  that 
I  have found anything in Mallard which could actually be described as a  full 
grown  bug.  At  times, I think that I have found a Mallard bug,  but  I  have 
always discovered later that it is my own faulty programming.

Mallard  BASIC  is less than perfect when it interacts with CP/M Plus.  If  an 
error occurs during a call to a CP/M Plus routine, it returns to the A> prompt 
after the message, instead of returning to Mallard BASIC. The simplest example 
of this is OPTION FILES "N" (instead of "M"). It is not too difficult to  find 
other  occurrences, mainly in disc handling routines. A strange one can  occur 
if  you  press [f5] to pause a program, and the decided to  [STOP].  Normally, 
everything  is  fine  but, if the [f5]-press coincides  with  some  CP/M  Plus 
routines, the [STOP] acts as in CP/M Plus and returns to A>.

In Section 4.2, we showed you how to save an alternative Mallard BASIC,  which 
runs from a 'warm' start. If you have this alternative, the accidental  return 
to  CP/M  Plus  is not nearly so troublesome, as you  can  usually  return  to 
Mallard  BASIC with your program intact. There is one problem that can be  far 
more disastrous than the above. I have only done it once... once is enough!

If  you make an error in saving a program, you will usually receive a  Mallard 
BASIC error message. No problem. Suppose that you have a write-protected  disc 
--  possibly  a master disc from Amstrad or some software  company.  You  have 
forgotten  to put in your programming disc, and leave the write-protected  one 
in.  The  SAVE goes to CP/M Plus before the write protect is found.  You  get: 
"Retry,  Ignore, or Cancel". It is the work of a moment to put in the  correct 
disc  and  R for Retry. DON'T DO IT!!! The routine uses the directory  of  the 
previous disc, and will probably overwrite bits and pieces of several programs 
that  you have on your own disc. Cancel returns to CP/M Plus, but this is  not 
so  bad, particularly if you have the warm-start Mallard BASIC  available.  Of 
course,  this  would never happen if you followed the rules, and  always  made 
back up copies of masters!

The  following is a 'STOP PRESS' addition. I think that there is an answer  to 
the  CP/M Plus problem in Mallard BASIC. Like a new drug, it should be  tested 
thoroughly to see whether there are any side effects. There has not been  time 
to  do  so, so you use it at your own risk. If you include it, the  CP/M  Plus 
error  system  is  changed, so that the message is  printed  but  the  program 
continues. The result is often a bit ugly, but your program is not  destroyed. 
After loading Mallard BASIC, POKE 64487,254 (This location is normally set  to 
0.).  Alternatively,  you  could load Mallard BASIC  and  make  the  following 

        POKE 803, 143 : POKE 384, 0
        DATA 14, 45, 30, 254, 205, 5, 0
        FOR n = 399 TO 405 : READ a : POKE n, a : NEXT

Then  use the method in Section 4.2 to save the new version, and  the  changed 
error trap will be installed when you use this version of Mallard BASIC.

Chapter 6: Style

The idea of this chapter is to pinpoint some of the main aspects in which  two 
good programmers might have totally opposing views. Programming style can lead 
to heated arguments, but I hope that we will keep temperatures at a reasonable 
level,  by putting both sides of most of the arguments -- even though you  may 
just  possibly  detect  that  I  am,  sometimes,  a  little  biased!  I  shall 
occasionally  go off on a red herring. Some of these topics are not  discussed 
elsewhere, and there are a few interesting little digressions to be made.

6.1 To REM or not to REM

Maybe this section is the unconverted preaching to the converted! Sometimes, I 
will REM a program, but it is not my normal practice. Occasionally, I write  a 
program that I want the user to study, and the program is so simple that a few 
bright  breezy REMs will explain all, even to the meanest intellect.  Usually, 
it  is  better  to document on-screen, in a separate DOC file,  or  on  paper. 
Sometimes,  it is better not to document at all -- you don't always  want  the 
user to understand your programs!

One  argument for REMs is that, if you have written a program some  time  ago, 
and  wish  to amend it, you will have forgotten why and how you did  this  and 
that,  and  REMs  will  help you to remember. If you find  this  is  true,  it 
justifies putting in REMs and using up space. Once upon a time, I was strongly 
anti-REM, but now I am indifferent. I don't particularly like porridge either, 
but don't let me stop you eating it!

There  is one rule that I do feel is worth keeping. Never GOTO or GOSUB  to  a 
REM  --  particularly if you are writing for a magazine. Those who  copy  your 
program  will probably leave out the REMs. I have no strong  feelings  whether 
you should use REM or ', since both work identically. If I use REMs, I  prefer 
to number operative lines in 10s, and put the REM lines with numbers ending in 
9.  Incidentally, I only realised the other day that you can generally  put  a 
REM or a ' in the middle of a line WITHOUT a preceding colon (":").

One  aspect of REM which differs in various dialects of BASIC is the  question 
of  whether a REM continues to the end of a line, or just to the  next  colon. 
Consider the line:

        20 REM Degrees to radians: DEF FN r (x) = x / 45 * ATN (1)

Mallard BASIC would ignore the DEF FN. In some BASICs, the second statement is 
operative.  I feel that this is preferable. To adjust REM so that  it  ignores 
only up to the next colon is not quite as easy as I thought it would be. It is 
necessary to adjust the editing process as well as the operation. However,  it 
is possible, but should be used with care. In Mallard 1.29:

        POKE  18720, 211 : POKE 18724, 0 : POKE 18725, 185 : POKE 18693, 37  : 
POKE 18539, 117

In Mallard 1.39, a better way is to change RMDIR to RM, and use RM for an  in-
line REM. This can be done by POKEing 20197-20200 with 9, 9, 9, and 205.

A use of a REM that may not be obvious is that, if you make your first program 
line REM followed by a long string of rubbish, it is possible to write machine 
code  into the rubbish without having to reserve memory elsewhere. In  Mallard 
1.29, the line 1 REM... enables you to POKE and CALL code from 31388  onwards. 
In Mallard 1.39, the relevant location is 31529.

Another  obscure and perhaps more interesting use of REM is to create a  self-
modifying  program. There is a school of thought that does not like a  program 
that changes itself. (ROCHE> Because the program cannot be put in ROM,  then.) 
I have found that, on one or two occasions, it is a very useful technique, and 
it  is certainly fun, not to say spectacular! The idea is to put  some  REM... 
lines  at  the start of the program, which will be ignored. Later  lines  POKE 
values  into the REM lines, and change the word REM into the appropriate  one. 
Finally,  the  rest  of the program is deleted, and the  converted  lines  are 
saved. This does need some knowledge of how a program is stored, but will  not 
usually require an understanding of machine code (See Section 9.3.).


I was once told by the boss of a software firm: "If you don't put an ON  ERROR 
trap throughout your programs, I will." He did. We had a complaint that Option 
1  mysteriously  ended with a prompt that should only occur in  Option  4.  On 
another  occasion,  the boss of another software firm refused to allow  me  to 
write  in  an  ON  ERROR trap. Who was right? I say  neither  was...  but  the 
variation shows up this matter of style.

ON  ERROR  is  a useful command if, and only if, it is used  with  a  specific 
purpose  in  mind. Once the specific hurdle is passed, the error  trap  should 
immediately  be cancelled. There are two distinct uses of the trap. The  first 
occurs  when an error may happen and, if it does happen, you want the  program 
to  continue,  and  no action to be taken. A specific example might  be  in  a 
general graph plotting program. A function such as y = SQR (x²-1) in the range 
x  =  -3 to x = 3 does not exist in the range -1 to 1. If the program  has  to 
plot points in this range, you want them to be ignored, rather than produce  a 
built-in error message.

The  other  type of trap is required when you ask for an input that  might  be 
incorrectly entered in several ways, a filename for example. Put in a  general 
error  trap,  with a RESUME taking you back to the original  prompt.  This  is 
simpler  than writing individual checks for everything that can go  wrong.  In 
this  type of error trap, you can use ERR to give extra prompts  for  specific 

Some  would say that it is obligatory for the programmer to provide  an  error 
trap for EVERYTHING that could possibly go wrong. My own view is that this, as 
with  so  many  aspects of programming, should be modified  according  to  the 
expected  ability  of  the user. If I write a program  to  calculate  standard 
deviations,  and ask for items of data entry, I will happily put INPUT  n.  If 
the  user gets a '?Redo from start' -- he, she, or it, should jolly well  know 
better! If I write a menu which requests the computer to print a nursery rhyme 
with a choice of 6 rhymes, I will put:

        INPUT n$ : n% = VAL (n$) : IF n% < 1 OR n% > 6 THEN...

where something polite follows the "THEN".

6.3 Long or short lines?

It is probably easier to read programs that consist of a single statement  per 
line. It is more efficient, in terms of compactness (and, very marginally,  in 
speed),  to use lines which include several statements. Two statements on  two 
lines  takes  four  bytes  more  than two statements  on  one  line.  The  two 
considerations  can  be balanced against each other. It is not a  question  of 
correctness, but of style.

I think that even the short-line programmers would conceded that  IF-THEN-ELSE 
lines  may  sometimes contain several statements. It can be avoided  by  IF... 
THEN  GOSUB 6100 ELSE GOSUB 6200. The subroutine at 6100 will contain all  the 
statements that a long-line writer would put between IF and THEN (followed  by 
RETURN). The subroutine at 6200 will contain the statements that would  follow 
the ELSE. As this does not make for easier reading, it is pretty pointless.

Generally,  I would side with the long-line programmers, although I would  not 
make  a  point of trying to cram as near to 255 bytes as possible  into  every 
line.  If  writing  a  program for publication on paper,  it  is  sensible  to 
consider how the program will appear. If the format is, say, 70 characters per 
line,  it  is worth taking a little trouble to see that no line  exceeds  this 
number,  and  that most lines do not fall too far short of it.  Even  in  this 
situation,  there  are  times  when  it will be  sensible  to  exceed  the  70 

Line  format also influence the ease or otherwise of debugging. If you are  an 
inexperienced  programmer,  or one liable to many typing errors,  short  lines 
make for easier debugging. It takes experience to be able to spot the  'Syntax 
Error in 60' if line 60 consists of 15 to 20 statements.

Short-line  programmers probably use long variable names. My main  reason  for 
disliking  long variable names is that it is easy to misspell them, and  hence 
they  cause confusion. When we were about 11 years old, we were all taught  to 
write  "Let Bill's age be x", so that we could write down and  manipulate  the 
resulting  equation.  Perhaps  the fashion now is  to  write:  3(BILLS.AGE+5)-

For those who like tidy programs with plenty of REMs, blank lines, and  single 
statements, indented loops (see below) are probably a glimpse of heaven! A few 
BASICs do put in the indentations themselves, but Mallard BASIC (rightly in my 
view) has no time for such frivolities. This does not stop you putting them in 
for yourself if you value presentation highly.

An indented loop might look like this:

        100 FOR n = 1 TO 20
               110 FOR m = 1 TO 15
                    120 a (n, m) = 0
                130 NEXT m
        140 NEXT n

(ROCHE> Rubbish! Under Mallard BASIC, indented loop looks like this:

        100 FOR n = 1 TO 20
        110     FOR m = 1 TO 15
        120         a (n, m) = 0
        130     NEXT m
        140 NEXT n

Simply press the [Space Bar] to insert spaces after the line number.)

You  may run up against something rather strange. If you indent in  unextended 
Mallard  BASIC,  there  is no problem. If you use either  DWBAS  or  Lightning 
BASIC,  the indentation will be edited out. Location 288 in the Mallard  BASIC 
interpreter contains an apparently innocent 0 to signal the end of part of the 
welcome  message. If it is POKEd to anything other than 0, the system of  line 
editing is changed so that ANY UNNECESSARY SPACES will not be contained in the 
program. As my algorithm for extensions did not cater for extra spaces at  the 
end  of  a line, I always put a POKE 288,1 into my extensions.  You  can  POKE 
288,0 and then put in indentations, but take care about silly spaces if you do 

6.4 Protection and OPTION RUN

Mallard  BASIC programs can, in theory, be protected from inspection  by  SAVE 
"XXX",p.  Whether it is worth doing this is another matter. The  only  Mallard 
BASIC program on the master disc, RPED, is protected. There are two reasons  I 
feel this is a mistake. There will be some Amstrad PCW owners who want to lean 
programming,  and  an  inspection of RPED shows some  useful  and  interesting 
tricks. It is probably more instructive than the demonstration programs in the 
manual. Secondly, users may want RPED to return to Mallard BASIC, or alter the 
keys  which  operate it. This is straightforward if you have  the  listing.  I 
cannot see that protecting RPED gives any advantage.

Since  several  ways have been published of breaking a  protected  program  -- 
Chapter  14  indicates the method that I use -- protection loses most  of  its 
value.  The  value of protection is presumably to prevent copying  and,  as  a 
protected Mallard BASIC program can be copied as easily as any other, it seems 
rather  pointless.  The  other value of protection is to  restrict  use  of  a 
program to certain users. In this respect, the Mallard BASIC protection system 
is far more foolproof than the CP/M Plus password protection, if you use it to 
best effect.

Imagine  that a businessman wants to keep confidential information  about  his 
customers. This is probably illegal under the "Data Protection Act", so you or 
I  would not do it, of course. This information is kept in a random file,  and 
only  certain  senior members must have access to it, although there  will  be 
times when less senior people have access to the computers and discs. You  are 
asked to help. You carefully code the information in the random file:

        C6P1  (CONF)
        500 INPUT "Enter details: ", b$ : b$ = UPPER$ (b$)
        510 c$ = "" : FOR n = 1 TO LEN (b$) : p = ASC (MID$ (b$, n))
        520 p = (n + 37) XOR p
        530 c$ = c$ + CHR$ (p) : NEXT

(ROCHE> Indented, this gives:

        500 INPUT "Enter details: ", b$
        510 b$ = UPPER$ (b$)
        520 c$ = ""
        530 FOR n = 1 TO LEN (b$)
        540     p = ASC (MID$ (b$, n))
        550     p = (n + 37) XOR p
        560     c$ = c$ + CHR$ (p)
        570 NEXT

This  is  an 'off the cuff' scrambling routine which should  be  difficult  to 
decipher,  and also reversible (interchange c$ and b$, and put a  final  PRINT 
instead of the initial INPUT).

Now,  you have to save your program "CONF" from prying eyes. In Mallard  1.29, 
you  may  have heard that, somewhere in memory, comes  the  rather  surprising 
message  'Acorn Computers'. This is actually part of the  protection  routine! 
First of all, we save CONF unprotected.

        1 INPUT "Enter your password: ", p$
        2 q$ = SPACE$ (16) : LSET q$ = p$
        3 FOR n = 0 TO 15 : POKE 22466 + n, ASC (MID$ (q$, n + 1)) : NEXT

(ROCHE> Indented, this gives:

        10 INPUT "Enter your password: ", p$
        20 q$ = SPACE$ (16)
        30 LSET q$ = p$
        40 FOR n = 0 TO 15
        50     POKE 22466 + n, ASC (MID$ (q$, n + 1))
        60 NEXT

We RUN this, and SAVE it as PASSWORD. LOAD CONF. Now, SAVE"CONF",P. It is  now 
impossible  to  RUN"CONF" unless PASSWORD is run and the correct  response  is 
given.  It  is probably wise to make CONF an OPTION RUN program  which  either 
returns to SYSTEM, or re-enters "Acorn Computers" in the region 22466-22481.

It  is  rather  sad, for Amstrad PCW9512 owners, that they  cannot  find  this 
entertaining message. Perhaps the programmers at Locomotive have had to put up 
with  too  many rude comments. All is not lost, however. If you  have  Mallard 
1.39, try this:

        10  FOR  n = 22644 TO 22659 : p = 256 - PEEK (n) : PRINT CHR$  (p);  : 

(ROCHE> Indented, this gives:

        10 FOR n = 22644 TO 22659
        20     p = 256 - PEEK (n)
        30     PRINT CHR$ (p);
        40 NEXT

The 1.39 coding contains a SUB (HL) for an ADD (HL), so that the message is  a 
little more difficult to find! With 1.39, 22644 should replace 22466 when this 
has been used above.

OPTION RUN is another technique often favoured by programmers for no very good 
reason.  I like to stop programs in the middle, from time to time. OPTION  RUN 
is  often  a  spoilsport  technique! There are times  when  it  is  necessary. 
Occasionally,  a  part of a program could crash badly if  [STOP]  was  pressed 
during  execution. At other times, an inexperienced user might be confused  -- 
for instance, if the cursor has been disabled.

The  main  reason  for OPTION RUN is that it is often  essential  to  do  some 
housekeeping before leaving the program. An obvious example is a program  that 
copies  files to M disc, updates them on M disc, and must copy them back to  A 
or  B  disc before ending. Also, if your program uses  Jetsam  keyed-files,  a 
[STOP] at a crucial moment may prevent the index and data elements of the file 
from being marked consistent, with results too horrifying to describe.

6.5 Where does one put subroutines?

Mr A will tell you that you should always put subroutines at the beginning  of 
your program. Mrs B will tell you to place your subroutines on higher-numbered 
lines  (but preferably only slightly higher numbers) than the lines that  call 
them. Let us dispose of this phantom stylistic point. Mr A and Mrs B were both 
right  when they started programming. Mr A's version of BASIC always  searched 
for  a  subroutine  from the first line of the program to the  last.  Mrs  B's 
version  started  from the current line, went to the end of the  program,  and 
then started again at the first line. In Mallard BASIC, it makes no difference 
(or only a negligible one). The interpreter replaces a line number in a  GOSUB 
or  a  GOTO  or RESTORE with an address in memory on  the  first  occasion  it 
executes  that  line. Each GOSUB, therefore, only needs a single  search,  and 
this is not going to make a significant difference in speed.

        10 GOSUB 40
        20 GOSUB 40
        30 END
        40 m = 31382 : IF PEEK (5103) = 197 THEN m = 31523
        50 FOR n = m TO m + 19 : PRINT PEEK (n); : NEXT : PRINT : RETURN

The subroutine prints out the PEEKs of the first two lines of the program. You 
will  see  that line 20 is altered after the line itself  has  been  executed. 
Originally, the two bytes after the 28 refer to line 40. Later, 28 becomes  29 
as  the signal that the change has been made, and the next two bytes  are  the 
ADDRESS of line 40.

The answer is to place your subroutines in the most convenient spots. I favour 
the  end of the program for general ones, and the end of a section  for  those 
that only apply to one section of the program.

6.6 GOTO taboo?

'BASIC  is  a  bad language because a  programmer  can  write  poorly-designed 
programs  that work by using GOTO.' This is what many computer theorists  have 
been  saying over the years. The argument is illogical, but that,  in  itself, 
does not necessarily excuse the use of GOTO.

There  is little doubt that beginners in BASIC do use GOTO when it  is  untidy 
and unnecessary. Programs that jump about all over the place are difficult  to 
read, and usually operate more slowly. A well-planned program should not  need 
many  GOTOs and, with experience, they can generally be avoided. I think  that 
the  academics would concede that it is not sensible to consider  IF-THEN-GOTO 
or  ON-GOTO  as  wrong. It is the unconditional GOTOs  that  set  their  blood 
racing!  My view is that, sometimes, GOTO is the natural way to  program,  and 
there  is  no point in being devious to avoid it. It can usually  be  done  by 
using an unnatural GOSUB or WHILE-WEND. But what's the point?

At  one time, RENUM was a luxury not included in most BASICs. It was  possible 
to  write  parts of a program, and then have an afterthought that  might  (for 
example) make the presentation better. There was not room to fit in the  extra 
coding  -- so GOTO 12000 and write it there. With a RENUM, you can squeeze  in 
the extra lines where they should logically be included. I am not sure that  I 
would  always  advise this. There are disadvantages in renumbering  a  program 
which have been discussed elsewhere.

I would advise programmers to examine every unconditional GOTO that they  use, 
and decide each case on individual merit! If this is done, your programs  will 
gradually become more structured, as some structure actually makes them easier 
to write.


PRINT is probably the first BASIC word we use. It is also, probably, the  last 
one  we  master! I fear that I never won any prizes for handwriting,  art,  or 
design -- so it is more than likely that you can create prettier screens  than 
I do.

The  best computer screens, like the best referees, are the ones that are  not 
noticed (graphic displays and high-tech games are exceptions). The user  wants 
screen information to be clear and concise. An untidy screen is a distraction. 
So  is  an  incorrect spelling, or a lack of  punctuation.  Your  very  clever 
programming  trick  may  be, as well. It is irritating  to  see  a  programmer 
showing off. The user is generally more interested in getting on with the  job 
that  the program does, than seeing beautifully-designed title pages.  If  the 
user is likely to run the program every day, he or she does not want to  stare 
at  "Written  by  Joe Bloggs" for ten seconds each time the  program  runs.  A 
comparison  between LocoScript and WordStar is worth making. You don't  notice 
the  screen in LocoScript, whereas most people would probably feel that it  is 
untidy in WordStar.

Having  made this point, the rest of my comments are only  suggestions,  which 
you  may or may not agree. When lengthy textual instructions have to be  given 
on  screen,  I  prefer printing on alternate lines,  and  usually  with  right 
justification.  An alternative is to write in short sentences, and centre  the 
text. Reverse video should be used sparingly, to emphasise important points or 
contrasts, and not just for decoration. PRINT USING is often helpful, but  one 
must be aware of the effects of silly inputs. It is sometimes helpful to title 
each  page with the option being performed. In most cases, the  screen  should 
not be seen to scroll. Blanking a screen by OUT 248,8 may give an instant  and 
attractive effect, but it has to be weighed against a moment of panic for  the 
user.  Titles  should  be centred, but options on a menu  should  be  aligned. 
Boxing-in text is tidy but, without a code routine, it can be annoyingly time-
consuming.  TABbing with full stops can often create a helpful effect.  Screen 
flashing  and  underlining are occasionally useful.  Consideration  should  be 
given to disabling the cursor.

Two little POKEs may be occasionally useful. POKE 24348,46 changes TAB so that 
it prints "..." instead of spaces. The 1.39 location is 24513 (46 is the ASCII 
code  for  a full stop). POKEing 17240 (17311 in 1.39) will change  the  input 
prompt from '?' to the ASCII character POKEd into this address.

Chapter 7: It pays to increase your word power

Up to this point, most references to Mallard BASIC reserved words have been to 
ones that you have used yourself. In this chapter, we discuss some words  that 
you probably do not use, and others that you may not use to their full extent. 
Mallard  BASIC  contains a great number of commands and functions.  There  are 
some that I have never used. Probably I never will use them. There are  others 
that seem to be redundant, but which eventually turn out to be powerful tools.

7.1 FIND$

We  start with FIND$, not because YOU ignore it, but because it took  ME  some 
time  before I recognised the full uses of this function! I imagine that I  am 
not the only one.

FIND$ is more flexible than most users may realise. For example, it can  carry 
out  a search on any disc by prefacing the filename with the disc name  and  a 
colon (":").

        n = 0 : WHILE n = 0 OR g$ = <> "" : n = n + 1 : g$ = FIND$ ("m:*.bas", 
n) : PRINT g$ : WEND

(ROCHE> Indented, this gives:

        n = 0
        WHILE n = 0 OR g$ = <> ""
              n = n + 1
              g$ = FIND$ ("m:*.bas", n)
              PRINT g$

This illustrates the point, giving a directory of all the BAS files  currently 
on the M disc. It also shows the use of the parameter n, and illustrates  that 
FIND$ can deal with wildcards. Maybe you knew this already, but I don't  think 
that  I picked it all up from my first read through the Mallard BASIC  manual. 
You can use DIR M:*.BAS instead of this routine.

There is one other point worth mentioning, regarding FIND$. It is only for the 
dedicated hackers! If you have had a successful FIND$, the details of the file 
found  will  lie  in the area 128-255 decimal. It is in the lap  of  the  gods 
whether  it  will  start at 128, 160, 192, or 224. The 32  bytes  include  the 
filename, where it resides on the disc, and the number of records. Try it,  if 
you are curious!

7.2 DEF FN

Most  Mallard BASIC programmers DO use DEF FN in escape sequences. Most  other 
programmers  do  not  use it at all! It is a powerful tool,  and  the  Mallard 
implementation  is  very  flexible. For non-mathematicians, the  idea  may  be 
difficult  at first sight, and this is probably why it is  generally  avoided, 
unless it can be copied from some other program.

A useful example is a routine to print a string in the centre of the screen.

        10 DEF FN t$ (t$) = SPACE$ (45 - LEN (t$) / 2) + t$

Make  t$  the  title  that you require, and PRINT FN t$  (t$)  will  print  it 
centrally.  Incidentally,  you  don't  have to use the  same  variable.  a$  = 
"ACCOUNTS."  :  PRINT  FN  t$  (a$)  will work,  and  so  would  PRINT  FN  t$ 

Earlier  BASIC  usually  included  DEF FN,  but  restricted  it  to  numerical 
variables.  Possibly  this  is  the  main  use.  Obvious  examples  come  from 
mathematical  functions  not included in the BASIC. For instance,  most  users 
will  not want the COSH function (except on dark nights in inner cities),  but 
mathematicians may. Simple:

        DEF FN cosh (x) = (EXP (x) + EXP (-x)) / 2

Finding  the  gradient  of a graph is a  standard  mathematical  procedure  in 
Calculus.  All  right, go on to the next section if you  are  shuddering  with 
horror!  The  program below shows how a DEF FN can be used  to  calculate  the 
gradient  of the graph 'y = x * x * x' at any point. FN a# (x) calculates  the 
value of y for any value of x that the user chooses to input. The function  is 
also used to calculate the gradient of a line between two points close to each 
other  on the curve. This gives a close approximation to the gradient  of  the 
tangent  at  the  actual  point chosen by the input  of  x.  Double  precision 
variables are used, since the values of 'y' at the two points will only differ 
by a small amount.

        10 DEFDBL x
        20 DEF FN a# (x) = x * x * x
        30 INPUT "Enter value of x "; x
        40 g# = (FN a# (x + 0.0001#) - FN a# (x)) / 0.0001#
        50 g = ROUND (g#, 2)
        60 PRINT "Gradient at (" x "," ROUND (FN a# (x), 2) ") is" g

We  shall return to this program in Chapter 9. It has a fundamental  fault  -- 
the fact that the program has to be changed for each curve -- that is not easy 
to  circumvent.  For the moment, it is just an example of how DEF FN  makes  a 
difficult process simple.

7.3 MID$ and VARPTR

Most programmers use MID$ quite frequently, even if they do not use it to  its 
full  potential.  MID$  is one of the few words which can be used  as  both  a 
command and a function and, once you have registered mentally that it can be a 
command, you will find that you can use it very neatly, on occasions. However, 
the  main  reason for including MID$ in this section is that  there  are  many 
occasions  when  MID$  is  used  where VARPTR  would  be  neater  and  quicker 
(sometimes  very, very much quicker). There is a powerful example of  this  in 
Section  4.2. ASC and MID$ could have been used for the same purpose,  but  it 
would have been much slower.

The  average  Mallard BASIC programmer does not use VARPTR, because  it  needs 
some  understanding of how the computer organises variables into  memory.  You 
don't  HAVE  to understand this to write good BASIC programs.  Even  so,  most 
Mallard  BASIC programmers are quite interested in this sort of  exercise.  As 
some understanding may aid programming, it is worth a look.

VARPTR  (a)  or  VARPTR  (a$)  will return a number  which  is  the  start  of 
information  about the relevant variable. If the variable is a string,  VARPTR 
will  point to three bytes of information. The first gives the length  of  the 
string.  The  second and third give the location of the start  of  the  string 
(Second + 256*third). An integer variable is also fairly straightforward.

        v = VARPTR (a%) : h = PEEK (v) + 256 * PEEK (v + 1)

This will return the value of a% in h. (Strictly speaking, UNT (h) will always 
equal a%.)

The  storage  of single (or double) precision variables is  more  complicated. 
Each  single precision variable is stored in 4 bytes. If you want to find  out 
how  this works, you may be able to do this for yourself. For those  who  want 
some  clues, try to understand this Mallard BASIC program, which  obtains  the 
value of a variable from the memory indicated by VARPTR.

        10 INPUT "Enter a number: ", a
        20 v = VARPTR (a) : b = PEEK (v + 3) : c = PEEK (v + 2)
        30 d = PEEK (v + 1) : e = PEEK (v)
        40 f = 2^(b - 128) : IF c > 127 THEN f = -f
        50 g = ((c AND 127) / 128 + d / 128 / 256 + e / 128 / 256 / 256)
        60 PRINT f / 2 * (1 + g)

The  idea of VARPTR is important if you use CALL with parameters. CALL v  (a%, 
b$,  c)  will put VARPTR (a%) in register HL, VARPR (b$) in register  DE,  and 
VARPTR (c) in register BC.

7.4 LSET and RSET

A recent magazine article (which was otherwise a good one) described LSET  and 
RSET  as  words  which could only be used to precede a PUT  in  random  filing 
programs.  This  is far from being the case. There is no reason why  LSET  (or 
RSET)  should  not  be used to format the output from a  sequential  file,  or 
indeed  any  other type of program. To use LSET outside a random file,  it  is 
necessary  to define a string first -- e.g. a$=SPACE$(20). Any  future  string 
LSET  into  a$ will be truncated to the first 20 characters if it is  over  20 
characters in length, and padded with spaces to 20 characters if it originally 
has less.

Similar effects can usually be achieved by the use of TAB or PRINT USING,  but 
LSET  or RSET are often convenient and neater. There is an example of this  in 
Section 6.4 but, once you have used LSET outside random filing, you will  find 
it is often useful. Another example is given in the next section.


BASICs  that I used before Mallard did not include INSTR. I don't say  that  I 
dismissed INSTR as an unneccesary extra, but I certainly did not recognize  it 
initially as an important programming function. It has already been  discussed 
in  INPUT  routines,  but I include it in this chapter as  I  feel  that  many 
Amstrad  PCW  programmers are not alive to its full use. It can be used  as  a 
search  function in files or programs, but I feel that a simpler  example  may 
serve  to  awaken  you  to its power. The  routine  below  is  a  dual-purpose 
illustration,  as it also contains a powerful example of using LSET outside  a 
random file!

        210 INPUT "Enter month: ", m$ : m = VAL (m$)
        220 n$ = SPACE$ (3) : LSET n$ = UPPER$ (m$)
        230 IF m = 0 THEN m = (INSTR (a$, n$) + 2) / 3
        240 IF m <> ROUND (m) OR m < 1 OR m > 12 THEN 210
        250 PRINT m

This is the hardest part of the date entry. It accepts a month, whether it  is 
entered as 3, MARCH, Mar, march or (as far as I can see) any legitimate entry. 
It will NOT accept MA, which is ambiguous. As I have said elsewhere, everybody 
but everybody writes date routines. Can YOU do it more neatly?

7.6 CHR$ and ASC

You  know  about CHR$ (27). You probably know that PRINT CHR$  (7)  makes  the 
computer beep. Make the computer print this: She said "I love you."

        PRINT "She said "I love you."

Won't work! You need:

        PRINT "She said " CHR$ (34) "I love you." CHR$ (34)

This is not the main point I want to make. CHR$ is not just something you  use 
if the computer is having a sulking fit and making you do things the hard way!

It is sometimes easier for the programmer to work in symbols. At other  times, 
it is easier to work in numbers. CHR$, and the reverse function ASC, translate 
from  one  to the other. The ASCII code gives a correspondence  between  every 
symbol  printed on the screen and a number between 0 and 255. Does it  horrify 
you  if I say that it is worthwhile to try to remember what ASCII codes  32-90 
represent?  If you do a considerable amount of programming, it will  pay  off, 

Most  programmers fight shy of ASC and CHR$ in anything but the  most  obvious 
situations. A good programmer takes advantage of them.

Some  programmers may not realise that Greek and mathematical  characters  are 
available  from  Mallard BASIC using ASCII codes under 32. To  produce  these, 
they have to be preceded by CHR$ (27). PRINT CHR$ (27) CHR$ (7), for  example, 
will  give the 'therefore' sign. Here is a little trick question: How  do  you 
obtain the DOWN arrow sign? The answer appears at the end of the chapter.


I was once told that the only reserved words I used were PEEK, POKE, and  CALL 
--  because  they were the only ones that I could spell  correctly.  Not  very 
nice!  Undoubtedly, your programs will be more powerful if and when you  learn 
to  incorporate machine code routines, but we are not including any advice  on 
this in the first part of this book.

This should not stop you PEEKing about in memory, a perfectly safe  procedure, 
although  it  may not mean very much. POKEing randomly is not so safe  --  you 
will  not damage the machine, but you may crash and have to switch off and  on 
again.  If you don't try, you won't learn. We have used POKEs quite freely  in 
places and, naturally, all these are intended to be safe.

If  you don't understand code, don't use CALL unless you are pirating it  from 
somebody  else.  USR is generally a more complicated way to achieve  the  same 
result  as  a CALL. Mallard includes it on the grounds of  compatibility  with 
earlier BASICs, rather than for its own sake.

7.8 LIST

LIST?  Yes, I have included this since few people use it in a PROGRAM.  It  is 
useful  in instruction or demo programs. It is not used because it  cannot  be 
included  successfully in a Mallard BASIC program, as LIST ends  the  program. 
You  can  change all this by a simple amendment that returns to  the  program, 
instead of the warm start address after a LIST. It does not even use a  single 
extra byte. I put this change in any extension that I make, but it is easy  to 
do yourself if you are using unextended Mallard. In 1.29:

        DATA 229, 205, 17, 71, 205, 136, 71, 205, 3, 75, 225, 201
        FOR n = 19191 TO 19202 : READ a : POKE n, A : NEXT

In Version 1.39:

        DATA 229, 205, 65, 71, 205, 200, 71, 205, 104, 75, 225, 201
        FOR n = 19292 TO 19303 : READ a : POKE n, A : NEXT

Answer to question in Section 7.6

PRINT CHR$ (27) CHR$ (9) gives the down arrow, BUT you have to use the command 
OPTION NOT TAB first, otherwise the above will be interpreted as a TAB.

Chapter 8: Writing for all PCWs

Many programmers write simply for their own use and pleasure. As you  progress 
with  programming, it is inevitable that you will want other users to be  able 
to  share your programs. The other users may be friends, fellow readers of  an 
Amstrad PCW magazine, or unknown customers who will want to buy your programs. 
There  are  differences  between the Amstrad  PCW8256,  PCW8512,  and  PCW9512 
(ROCHE>  And the PcW9256, PcW9512+, and PcW10!) that have to be considered  if 
your  version  is going to run on each machine, and use the  extra  facilities 
that  the bigger machines provide. This chapter discusses some of  the  points 
that you will have to think about.

8.1 Mallard 1.29 and 1.39

When the Amstrad PCW9512 was produced, assurances were given that it would run 
programs  written  for the other machines in Mallard BASIC and CP/M  Plus.  In 
general, this is true. The machine runs a different version of CP/M Plus,  but 
most of the standard addresses are the same and, provided a CP/M program  does 
not  try to communicate directly with the BIOS, it will probably run  on  both 
machines. Our first concern is the different version of Mallard BASIC.

You  can  use  Mallard BASIC Version 1.39 on an  Amstrad  PCW8256  or  PCW8512 
without  any apparent difficulty. As far as I know, you can use Mallard  BASIC 
Version  1.29 on the Amstrad PCW9512. For instance, the routine for  protected 
programs has been tightened in Mallard BASIC Version 1.39. There may be  other 
similar modifications. Mallard BASIC Version 1.39 does use 141 extra bytes.

If  you  never PEEK, POKE, or CALL, there is only one point  to  worry  about, 
which  is  that Version 1.39 has various extra reserved words. This  might  be 
useful  if  they  actually did something -- but  they  don't!  Possibly,  they 
prepare  for an update, since the words are similar to those used by a  16-bit 
machine  running  MS-DOS. If you use RMDIR in a 1.39 program, it will  act  in 
nearly the same way as REM. There is a small problem this does create. The new 
words cannot be used as variables. Three of them are CD, MD, and RD. It is not 
stretching  imagination too far to say that you are quite likely to have  used 
these  in a program. CD might be used for the Current Date, for  example.  You 
may  get an error, e.g. PRINT CD. You may get ignored, e.g. CD = VAL  (date$). 
The other words are RMDIR, CHDIR, MKDIR, FINDDIR$, and CHDIR$.

        10 x = 19820 : IF PEEK (5103) = 197 THEN x = 19966
        20 PRINT " "; : FOR n = 90 TO 65 STEP -1 : IF PEEK (x) THEN PRINT CHR$ 
(8) CHR$ (n);
        30 p = 1 : WHILE p : p = PEEK (x) : x = x + 1
        40 IF p = 9 THEN p = 46
        50 IF p > 127 THEN p = p - 128 : PRINT CHR$ (p), CHR$ (n); : x = x + 1 
: GOTO 70
        60 PRINT CHR$ (p);
        70 WEND
        80 NEXT : PRINT CHR$ (8) " " : END

(ROCHE> Indented, this gives:

        10 x = 19820
        20 IF PEEK (5103) = 197 THEN x = 19966
        30 PRINT " ";
        40 FOR n = 90 TO 65 STEP -1
        50     IF PEEK (x) THEN PRINT CHR$ (8) CHR$ (n);
        60     p = 1
        70     WHILE p
        80           p = PEEK (x)
        90           x = x + 1
        100          IF p = 9 THEN p = 46
        110          IF p > 127 THEN p = p - 128 : PRINT CHR$ (p), CHR$ (n); : 
x = x + 1 : GOTO 130
        120          PRINT CHR$ (p);
        130    WEND
        140 NEXT
        150 PRINT CHR$ (8) " "
        160 END

This  little program will give you a list of all the reserved words in  either 
version of Mallard BASIC. If you want hard copy (ROCHE> Difficult, since  this 
program generates ASCII BackSpace (08H) characters!), you could change all the 
PRINTs  to LPRINT, or use the POKE we told you about to do this. A  full  stop 
(".") will be printed in those words where a space is optional. (For  example, 
GO TO and GOTO are accepted.) The test in line 10 is to check which version is 
used,  and  the start address of the word table is altered  for  1.39  (ROCHE> 
There  is a "word table" before the abbreviated keywords, but it is not  used, 
here.  Those 26 addresses correspond to Z-A (not A-Z).). As far as I can  see, 
the tables are identical, apart from the words mentioned above.

The  major problems with the 1.39 version will not affect you, unless you  use 
PEEKs  and  POKEs to the interpreter, or machine code that makes  use  of  the 
routines  resident  in the interpreter. If you can find a routine to  do  your 
work  in 1.29, there will be a similar one in 1.39, but the start may be at  a 
slightly (or very) different address in 1.29. For instance, v=3757 : CALL v... 
gives  you  "Improper Argument" in 1.29. You need v=3756 to do this  in  1.39. 
Addresses of routines up to about 3000 appear to be identical in both  Mallard 
BASICs but above that, normally, there is a difference. Usually, the  routines 
are identical, but you cannot absolutely rely on this. One favourite of mine -
- v=4931 : CALL v -- evaluates from HL, and puts the answer in register A  (0-
255).  In 1.29, the answer is echoed in register E but, in 1.39, the  original 
value  of register E is replaced. Hence, everything needs checking if a  part-
code program is to work in both versions.

It  is  also just possible that you have written programs that use  PEEKs  and 
POKEs into the program to modify it. If you do this, you will have to remember 
that the program start is normally 31382 in 1.29, and 31523 in 1.39. You  need 
not worry about any POKEs that we give you in this book. If they are different 
in 1.39, it has been mentioned.

Obviously, the different printer is another problem with the Amstrad  PCW9512. 
The  answer is to keep printing as simple as you can, by keeping escape  codes 
to  the minimum. Some codes are different on the Amstrad PCW9512,  unless  you 
tell the daisy-wheel printer that it is a dot-matrix by LPRINT CHR$ (27)  "@". 
Full  graphic  screen dumps are impossible, unless the Amstrad PCW9512  has  a 
dot-matrix printer attached -- this is possible, but rather eccentric!

8.2 Using the discs available

If  you are programming for yourself, it is easy to know how you will use  the 
disc  system  that you have. If you are writing more generally,  your  program 
should  adapt  itself to use what discs are available. It looks as  though  it 
should  be  an  easy problem to find out how many drives  are  fitted  to  the 
computer running the program. It is easy enough if you ask for an input by the 
user -- but this suggests a lack of professionalism.

If  the  program will need to chain other programs, or make  use  of  existing 
files,  it is sensible to use the M disc for anything at all complex.  Section 
4.3 shows a method of doing this. The housekeeping is done at the start of the 
program,  and  all the necessary discs are transferred to M. On  quitting  the 
program,  any  altered files are transferred to the disc on  which  they  were 

One  solution is to use ON ERROR traps. Look for a file on the A drive. If  an 
error  occurs,  trap  it and look on B. This might cause  untidiness  with  an 
Amstrad  PCW8256, as a request to search the B drive usually brings  a  rather 
meaningless prompt.

        10 h = HIMEM : j = h - 15 : MEMORY j - 1
        20 DATA 229, 205, 90, 252, 230, 0, 230, 1, 60, 225, 119, 201
        30 RESTORE 20 : FOR n = j TO j + 11 : READ a : POKE n, a : NEXT
        40 d% = 0 : CALL j (d%) : MEMORY h

This  routine  may  be  the answer. The variable d% will be set  to  1  or  2, 
depending  on  the  number of drives. Different routines and  prompts  can  be 
written for the two cases. An alternative approach is to use the fact that, on 
the Amstrad PCW8256/8512, PEEKing 65359/65360 will give you the size of the  A 
disc,  and  PEEKing 65413/65414 gives the size of the M disc. On  the  Amstrad 
PCW9512, the addresses are 65309/65310 and 65492/65493, respectively.

One disadvantage of using the M disc for all the program files is that it will 
often  be necessary to use OPTION RUN for at least part of the  program.  This 
will  ensure that any files that need to be saved to a physical disc  will  be 
saved when the Quit option is requested.

8.3 Machine code and compilers

If  you  are writing for other users, you will have to face  the  question  of 
whether Mallard BASIC is an adequate language for the purpose. To some extent, 
this  must  depend on the application. For example, I do not  think  that  you 
could  write a commercially viable arcade game using Mallard BASIC  alone.  At 
the same time, I dislike the comments of the superior sniffers... "It is  only 
written  in  BASIC..."  "Pascal is my language.  So  structured,  compared  to 
BASIC..."  "I  have  never used a high-level language.  Assembler  just  comes 

Mallard  BASIC  is  perfectly  adequate  for  the  majority  of  Amstrad   PCW 
applications  that  you are likely to write. That is not to say  that  someone 
else  could not write a superior version in a different language. However,  it 
is  generally better to write a good program in a language that you know  well 
than to use a language that may be more suitable, but that you cannot  exploit 
to  the same extent as Mallard BASIC. There is not much that you cannot do  in 
Mallard BASIC, with a little ingenuity.

The main drawback of Mallard BASIC is its speed. It is not as great a  problem 
as  many  experts would have us believe. Computers work at high speed  in  any 
language, and many applications will work faster than the user can appreciate. 
In  many  programs, the only delays will occur when loading from disc,  and  a 
change of language is not going to make any difference. Besides, Mallard is  a 
very fast version of BASIC. While Mallard is fundamentally an interpreter,  it 
does  act as a partial compiler. Some compiling is done when a line is  edited 
into  a  program. More happens after "RUN", before the program  executes,  and 
some is done on the first execution of a program line.

If  more speed is needed, there are two broad solutions. The first is  to  use 
machine code for those parts of the program which run slowly. The second is to 
use a compiled language.

Personally,  I  prefer the first solution. Mallard BASIC interacts  well  with 
machine code. We shall show various examples of this in the second part of the 

Using  a  compiled  language  does not always  mean  that  code  routines  are 
irrelevant.  Just  because  Mallard  BASIC  does  not  access  high-resolution 
graphics, it does not mean that your compiled language will do so! Indeed, you 
may  well find that the compiled language has not been  specifically  designed 
for the Amstrad PCW, and may not interact so well with the machine.

A  compiler should be quicker than an interpreter -- in theory.  In  practice, 
this will usually be so, but the compiled code may not be very efficient,  and 
I  have heard that some compiled language (CBASIC Compiler, for  example)  for 
the  Amstrad  PCW  are, actually, slower than Mallard  BASIC  in  places.  The 
compiler  is  unlikely to execute as quickly as a  custom-built  machine  code 
routine. This is not the place to discuss the relative merits of Forth,  Logo, 
Pacal,  C,  etc...,  some of which are compiled languages.  I  have  heard  of 
several Mallard BASIC programmers who have 'progressed' to new languages, only 
to return to Mallard BASIC in the end.

Is  there a way of compiling a Mallard BASIC program? As far as I know,  there 
is  no direct Mallard BASIC compiler, and certainly no compiler that can  deal 
with  Jetsam. However, MBASIC can be compiled, and is very similar to  Mallard 
BASIC. Trying to write and edit in MBASIC makes the Mallard BASIC editor  look 
100 years ahead of its time, but it is possible to write a program in  Mallard 
BASIC,  save  it in ASCII, and run it in MBASIC. To do this,  you  must  avoid 
certain  words, such as FIND$, that do not operate in MBASIC. You have to  use 
USR instead of CALL, the compiler cannot deal happily with reserving space for 
code,  and INPUT is user-megahostile. After all this, the program  takes  more 
space and does not run that much faster!

Chapter 9: Intermission!

This chapter does not quite fit into Part 1. Nor is it part of the guided tour 
of  memory  that we shall give you in Part 2. Hence the title! The  first  two 
sections  discuss  problems  that you might expect  Mallard  BASIC  to  solve, 
problems  that, on some computers, BASIC CAN solve. We find solutions,  but  a 
little  code is needed in both sections. In contrast, the third section  shows 
you  some ideas that you probably did not imagine were possible in  'straight' 

9.1 Can you INPUT a function?

To start this chapter, I want to discuss a very old problem that I had with  a 
different  BASIC.  It  also happens in Mallard BASIC. You  may  remember  this 
program in the DEF FN section:

        10 DEFDBL x
        20 DEF FN a# (x) = x * x * x
        30 INPUT "Enter value of x "; x
        40 g# = (FN a# (x + 0.0001#) - FN a# (x)) / 0.0001#
        50 g = ROUND (g#, 2)
        60 PRINT "Gradient at (" x "," ROUND (FN a# (x), 2) ") is" g

The unsatisfactory feature of this progrm is that, while the program will give 
the gradient at any point on y = x3, it will not operate for any other  curve, 
unless  line  20  is changed. To expect the user to  rewrite  the  program  is 
definitely 'user unfriendly'. What we require is:

        20 INPUT "Enter your function in terms of x: ", FN a# (x)

Mallard  BASIC  does  not allow this. Nor is it any use  trying  to  input  an 
ordinary string and using VAL to evaluate it. This is not a problem that  will 
worry  every programmer, but it is a serious shortcoming if you wish to  write 
programs that work in a general mathematical situation. It is not an incorrect 
answer  to say that what we are trying to do is impossible in  Mallard  BASIC, 
and  that,  if  we want to be able to input a function, we will  have  to  use 
another  implementation of BASIC, or another language. (ROCHE> Like Dr.  Logo, 
running on the Amstrad PCWs.) However, mountains are there to be climbed,  and 
there is a lot of satisfaction in making a computer do what 'it can't do'!

I am going to use two different approaches to the problem. The first is to  do 
a few conjuring tricks, and come to a solution with a program that scores high 
marks  for  eccentricity and ingenuity. However, it could only be said  to  be 
user  friendly if compared to the original. It does contain some machine  code 
to set an expansion key but, in practice, you could use SETKEYS to do this. We 
put the INPUT string, together with necessary additions, into the buffer which 
is  normally  used by Mallard BASIC in the EDIT routine. The EDIT  routine  is 
then  intercepted, and the program has written a line of its own into  itself. 
That  stops  it! This is why we need the slightly unfriendly method  of  PRESS 
[COPY] KEY to continue.

        5 e$ = CHR$ (27) : c$ = e$ + "E" + e$ + "H" : DEFDBL x
        10  h  = HIMEM : v = 51200! : MEMORY v - 1 : DATA 78, 26, 71,  22,  3, 
205, 90, 252, 215, 0, 201
        20 DATA 6, 156, 14, 10, 33, 40, 200, 205, 90, 252, 212, 0, 201
        30 DATA 13, 71, 79, 84, 79, 32, 49, 48, 48, 13
        35 FOR n = 0 TO 10 : READ a : POKE 51200! + n, a : NEXT
        36 FOR n = 0 TO 12 : READ a : POKE 51220! + n, A : NEXT
        37 FOR n = 0 TO  9 : READ a : POKE 51240! + n, a : NEXT
        40 u = 51220! : CALL u
        50 a% = 11 : b% = 156 : CALL v (a%, b%) : MEMORY h
        51 PRINT c$ : INPUT "Enter your function: ", f$
        52 f$ = "100 DEF FN a# (x) = " + f$ + CHR$ (0) : bu = PEEK (511) + 256 
* PEEK (512) - 1
        53 FOR n = 1 TO LEN (f$) : POKE bu + n, ASC (MID$ (f$, n)) : NEXT
        54 PRINT : PRINT "Now, press the [COPY] key."
        55 v = 510 : CALL v
        100 DEF FN a# (x) = x * x
        110 PRINT c$ : INPUT "Enter value of x "; x
        120 g# = (FN a# (x + 0.0001#) - FN a# (x)) / 0.0001# : g = ROUND  (g#, 
        130 PRINT "Gradient at (" x "," ROUND (FN a# (x), 3) ") is" g
        140 PRINT "Press X for new value of x, E to edit function, Q to quit."
        150 z$ = UPPER$ (INPUT$ (1)) : i = INSTR ("XEQ", z$) : ON i GOTO  110, 
51, 160 : GOTO 150
        160 END

(ROCHE> $$$$)

Up  to line 50, we are setting the [COPY] key to RETURN, followed by GOTO  100 
and  a  further RETURN. The technique will be discussed in greater  detail  in 
Chapter  16. Lines 51-54 write the input function into line 100, and then  the 
[COPY] key does a GOTO 100 to restart.

Whatever you may think of that program, it does not entirely satisfy  criteria 
of user friendliness, and the only entirely satisfactory answer is to  rewrite 
the  BASIC. (ROCHE> Dr. Logo for the Amstrad PCW is a functional language,  so 
interpreting  a function is childplay for it. BASIC is a procedural  language, 
so  is  a little bit lower level than Dr. Logo. In this case,  it  is  clearly 
better to leave Mallard BASIC and use Dr. Logo. Each programming language  has 
advantages  and  drawbacks. When you know several  programming  language,  you 
select  the  one better suited for the job.) I am going to include  a  program 
that  just uses one of the extras in Lightning BASIC -- I am not  cheating,  I 
wrote the code for it myself (ROCHE> Yes, you are cheating, since you are  the 
only  one who has it...) -- namely, an extension to EXP so that, if  a  string 
variable is entered, it will evaluate it as though it were a function. The EXP 
is very similar to EVAL in BBC or XTAL BASICs. I feel that it should have been 
included  in  Mallard  BASIC. It is obviously a waste of  time  entering  this 
program  if  you do not have Lightning BASIC. (ROCHE> Then,  why  publish  it? 

        10 DEFDBL x
        20 INPUT "Enter function in terms of x: ", a$
        30 INPUT "Enter value of x: "; x : x1 = x
        40 b# = EXP (a$) : x = x + 0.0001# : c# = EXP (a$) : g# = (c# - b#)  / 
0.0001# : g = ROUND (g#, 3)
        50 PRINT "Gradient at (" x1 "," ROUND (b#, 3) ") is " g
        60 PRINT "Press X for new value of x, E to edit function, Q to quit."
        70  z$ = UPPER$ (INPUT$ (1)) : i = INSTR ("XEQ", z$) : ON i  GOTO  30, 
20, 80 : GOTO 70
        80 END

(ROCHE> Re-arranged, this gives:

        10 DEFDBL x
        20 INPUT "Enter function in terms of x: ", a$
        30 INPUT "Enter value of x: "; x
        40 x1 = x
        50 b# = EXP (a$)
        60 x = x + 0.0001#
        70 c# = EXP (a$)
        80 g# = (c# - b#) / 0.0001#
        90 g = ROUND (g#, 3)
        100 PRINT "Gradient at (" x1 "," ROUND (b#, 3) ") is " g
        110 PRINT "Press X for new value of x, E to edit function, Q to quit."
        120 z$ = UPPER$ (INPUT$ (1))
        130 i = INSTR ("XEQ", z$)
        140 ON i GOTO 30, 20, 150 : GOTO 120
        150 END

9.2 UDG program

The  program  below is more substantial than those included so far. It  is  an 
example  of  a  techique  that we mentioned in  passing  --  a  self-modifying 
program. It shows in practice some of the ideas that have been given to you in 
theory. It is not possible to access the screen character set without a bit of 
code.  (This  will be discussed in greater detail in Part 2,  so  don't  worry 
about it now.) We shall study the program in the order in which it was written 
-- at least you will see how madness has its methods!

The  idea  is  to create a new program from an old  one.  Using  the  original 
program,  UDGs (User-Defined Graphics) are created in the range CHR$  (192)  - 
CHR$  (207). On exit from the original, you are told to SAVE, and  the  result 
can be the start of your own program using the new graphics.

        1 DATA 00,00,00,00,00,00,00,00
        2 DATA 00,00,00,00,00,00,00,00
        3 DATA 00,00,00,00,00,00,00,00
        4 DATA 00,00,00,00,00,00,00,00
        5 DATA 00,00,00,00,00,00,00,00
        6 DATA 00,00,00,00,00,00,00,00
        7 DATA 00,00,00,00,00,00,00,00
        8 DATA 00,00,00,00,00,00,00,00
        9 DATA 00,00,00,00,00,00,00,00
        10 DATA 00,00,00,00,00,00,00,00
        11 DATA 00,00,00,00,00,00,00,00
        12 DATA 00,00,00,00,00,00,00,00
        13 DATA 00,00,00,00,00,00,00,00
        14 DATA 00,00,00,00,00,00,00,00
        15 DATA 00,00,00,00,00,00,00,00
        16 DATA 00,00,00,00,00,00,00,00
        49 GOTO 100
        50 h = HIMEM : j = INT ((h + 1) / 256) - 1 : k = j * 256 : udg = k
        60  FOR n = k + 128 TO k + 255 : READ a$ : a = VAL ("&H" + a$) :  POKE 
n, a : NEXT
        70  DATA 1, 9, 192, 205, 90, 252, 233, 0, 201, 6, 128, 33,  128,  192, 
17, 0, 190
        71 DATA 26, 78, 119, 121, 18, 19, 35, 16, 247, 201
        80  FOR n = k TO k + 26 : READ a : POKE n, a : NEXT : POKE k + 2, j  : 
POKE k + 13, j
        90 CALL udg
        100 e$ = CHR$ (27) : c$ = e$ + "E" + e$ + "H"
        101 DEF FN a$ (r, c) = e$ + "Y" + CHR$ (32 + r) + CHR$ (32 + c)
        110  d = 191 : MEMORY ,,3 : p = 31388 : IF PEEK (5103) = 197 THEN p  = 
        150 k$ = CHR$ (1) + CHR$ (6) + CHR$ (31) + CHR$ (30)
        160 DIM k (7) : GOSUB 300
        170 PRINT c$; "The UDGs are ready to be saved."
        171 PRINT : PRINT "DON'T FORGET TO DO SO!" CHR$ (7)
        180 DELETE 49,190
        190 DELETE 100-1000,65000
        200 z = 0 : r = 0 : c = 0 : a = 127 : b = 128 : WHILE z <> 27 :  PRINT 
FN a$ (13 + r, 41 + c);
        205 z$ = INPUT$ (1) : z = ASC (z$)
        210 i = INSTR (k$, z$) : ON i GOSUB 700, 710, 720, 730
        220 IF z = 32 THEN GOSUB 740 : ELSE IF z > 32 THEN GOSUB 750
        230 b = 2^(7 - c) : a = 255 - b : WEND
        240 RETURN
        290 :
        300 WHILE d < 207 : d = d + 1
        305 PRINT c$ : PRINT "CHARACTER NUMBER"; d
        310  PRINT  :  PRINT  "Press Y to design it, X to  end,  C  to  change 
        320 z$ = UPPER$ (INPUT$ (1)) : i = INSTR ("YXC", z$)
        321 ON i GOTO 340, 370, 330 : GOTO 320
        330 PRINT : INPUT "Enter number between 192 and 207: "; d
        331 IF d < 192 OR d > 207 THEN 330
        340 GOSUB 800 : GOSUB 200
        345 FOR n = O TO 7 : s$ = "00" + HEX$ (k (n)) : s$ = RIGHT$ (s$, 2)
        350 u = ASC (s$) : v = ASC (RIGHT$ (s$, 1)) : q = p + (d - 192) * 30 + 
n * 3
        360 POKE q, u : POKE q + 1, v : NEXT : WEND
        370 RETURN
        690 :
        700 c = c - 1 - (c = 0) : RETURN
        710 c = c + 1 + (c = 7) : RETURN
        720 r = r - 1 - (r = 0) : RETURN
        730 r = r + 1 + (r = 7) : RETURN
        739 :
        740 k (r) = k (r) AND a : PRINT " "; : GOTO 760
        750 k (r) = k (r) OR b : PRINT CHR$ (188);
        760 c = c + 1 : IF c = 8 THEN c = 0 : r = r + 1 : IF r = 8 THEN r = 0
        770 RETURN
        790 :
        800 PRINT FN a$ (12, 40); CHR$ (134) STRING$ (8, 138) CHR$ (140)
        810  FOR n = 0 TO 7: PRINT FN a$ (13 + n, 40); CHR$ (133)  SPACE$  (8) 
CHR$ (133) : NEXT
        820 PRINT FN a$ (21, 40); CHR$ (131) STRING$ (8, 138) CHR$ (137)
        821 PRINT FN a$ (25, 0); "Use cursor keys to move."
        822 PRINT "Spacebar for unlit pixel."
        823  PRINT "Any LETTER key to light pixel." : PRINT "Use  [EXIT]  when 
        830 FOR n = 0 TO 7 : k (n) = 0 : NEXT : RETURN
        65000 END

(ROCHE> Indented, this gives:

        10 DATA 00,00,00,00,00,00,00,00
        20 DATA 00,00,00,00,00,00,00,00
        30 DATA 00,00,00,00,00,00,00,00
        40 DATA 00,00,00,00,00,00,00,00
        50 DATA 00,00,00,00,00,00,00,00
        60 DATA 00,00,00,00,00,00,00,00
        70 DATA 00,00,00,00,00,00,00,00
        80 DATA 00,00,00,00,00,00,00,00
        90 DATA 00,00,00,00,00,00,00,00
        100 DATA 00,00,00,00,00,00,00,00
        110 DATA 00,00,00,00,00,00,00,00
        120 DATA 00,00,00,00,00,00,00,00
        130 DATA 00,00,00,00,00,00,00,00
        140 DATA 00,00,00,00,00,00,00,00
        150 DATA 00,00,00,00,00,00,00,00
        160 DATA 00,00,00,00,00,00,00,00
        170 GOTO 360
        180 h = HIMEM
        190 j = INT ((h + 1) / 256 - 1
        200 k = j * 256
        210 udg = k
        220 FOR n = k + 128 TO k + 255
        230     READ a$
        240     a = VAL ("&H" + a$)
        250     POKE n, a
        260 NEXT
        270 DATA 1, 9, 192, 205, 90, 252, 233, 0, 201, 6, 128, 33,  128,  192
        280 DATA 17, 0, 190, 26, 78, 119, 121, 18, 19, 35, 16, 247, 201
        290 FOR n = k TO k + 26
        300     READ a
        310     POKE n, a
        320 NEXT
        330 POKE k + 2, j
        340 POKE k + 13, j
        350 CALL udg
        360 e$ = CHR$ (27)
        370 c$ = e$ + "E" + e$ + "H"
        380 DEF FN a$ (r, c) = e$ + "Y" + CHR$ (32 + r) + CHR$ (32 + c)
        390 d = 191
        400 MEMORY ,,3
        410 p = 31388
        420 IF PEEK (5103) = 197 THEN p  = 31529
        430 k$ = CHR$ (1) + CHR$ (6) + CHR$ (31) + CHR$ (30)
        440 DIM k (7)
        450 GOSUB 680
        460 PRINT c$; "The UDGs are ready to be saved."
        470 PRINT
        480 PRINT "DON'T FORGET TO DO SO!" CHR$ (7)
        490 DELETE 170,500
        500 DELETE 360-1170,1180
        510 z = 0
        520 r = 0
        530 c = 0
        540 a = 127
        550 b = 128
        560 WHILE z <> 27
        570       PRINT FN a$ (13 + r, 41 + c);
        580       z$ = INPUT$ (1)
        590       z = ASC (z$)
        600       i = INSTR (k$, z$)
        610       ON i GOSUB 940, 950, 960, 970
        620       IF z = 32 THEN GOSUB 990 : ELSE IF z > 32 THEN GOSUB 1000
        630       b = 2^(7 - c)
        640       a = 255 - b
        650 WEND
        660 RETURN
        670 :
        680 WHILE d < 207
        690       d = d + 1
        700       PRINT c$
        710       PRINT "CHARACTER NUMBER"; d
        720       PRINT
        730       PRINT "Press Y to design it, X to end, C to change number."
        740       z$ = UPPER$ (INPUT$ (1))
        750       i = INSTR ("YXC", z$)
        760       ON i GOTO 340, 370, 330 : GOTO 740
        770       PRINT
        780       INPUT "Enter number between 192 and 207: "; d
        790       IF d < 192 OR d > 207 THEN 770
        800       GOSUB 1040
        810       GOSUB 510
        820       FOR n = O TO 7
        830           s$ = "00" + HEX$ (k (n))
        840           s$ = RIGHT$ (s$, 2)
        850           u = ASC (s$)
        860           v = ASC (RIGHT$ (s$, 1))
        870           q = p + (d - 192) * 30 + n * 3
        880           POKE q, u
        890           POKE q + 1, v
        900       NEXT
        910 WEND
        920 RETURN
        930 :
        940 c = c - 1 - (c = 0) : RETURN
        950 c = c + 1 + (c = 7) : RETURN
        960 r = r - 1 - (r = 0) : RETURN
        970 r = r + 1 + (r = 7) : RETURN
        980 :
        990 k (r) = k (r) AND a : PRINT " "; : GOTO 1010
        1000 k (r) = k (r) OR b : PRINT CHR$ (188);
        1010 c = c + 1 : IF c = 8 THEN c = 0 : r = r + 1 : IF r = 8 THEN r = 0
        1020 RETURN
        1030 :
        1040 PRINT FN a$ (12, 40); CHR$ (134) STRING$ (8, 138) CHR$ (140)
        1050 FOR n = 0 TO 7
        1060     PRINT FN a$ (13 + n, 40); CHR$ (133)  SPACE$  (8) CHR$ (133)
        1070 NEXT
        1080 PRINT FN a$ (21, 40); CHR$ (131) STRING$ (8, 138) CHR$ (137)
        1090 PRINT FN a$ (25, 0); "Use cursor keys to move."
        1100 PRINT "Spacebar for unlit pixel."
        1110 PRINT "Any LETTER key to light pixel."
        1120 PRINT "Use  [EXIT]  when finished."
        1130 FOR n = 0 TO 7
        1140     k (n) = 0
        1150 NEXT
        1160 RETURN
        1170 :
        1180 END

Lines 1-16 were written first. These provide the part of the program that will 
be  altered.  The reason for writing them first is that they don't  take  much 
copying with clever use of AUTO and RENUM. Work it out for yourself!

I  then  wrote the subroutine at 800, to put a grid on the page. Some  of  the 
lines  here  were afterthoughts. Now comes the main subroutine  at  200.  This 
allows designing on the grid, by use of the cursor keys and others. A point to 
note  is that the input key is recognized by z$ or by z, its ASCII  code.  The 
array K(7) contains the binary representation of each row at the current state 
of  play. You will notice the use of INSTR and the overuse (?) of  subroutines 
in the 700-799 range.

The 700+ subroutines are neat (although I say it myself)! IF is avoided in the 
checks whether the cursor goes off the grid by using logical expressions.  The 
AND  and  OR  in 740 and 750 are worth noting. It is also  a  point  that  the 
UNCONDITIONAL GOTO in line 740 makes for tidy and structured coding!!!

At  this point, it was time for a checking (the grid had already been  tested) 
and  so a few lines in the 100-section were needed. Nothing too disastrous  -- 
the cursor went up when it should have gone down, and a few inevitable  syntax 
errors. On to the routine at 300, which is the nub of the program.

300-340 are routine stuff. 345-360 do the modification. The k() array contains 
the  vital numbers but, first, they have to be turned into hex, and  each  two 
digits poked into the correct place in the first 16 lines.

All  that  is left -- after testing this -- is to finish off the  program,  so 
that it ends tidily, deletes what is unwanted, and prompts to save. That, plus 
the  machine code routine in lines 50-99, which will be required in the  saved 
amemdment.  This  code first reads the data into an appropriate  part  of  the 
memory,  and  then  includes a machine code routine  to  change  the  relevant 
characters. It calls it by CALL udg. This code is a toggle, so that, when  you 
have  finished  your  own  program, you can CALL  udg  again  to  restore  the 
character set to normal.

A  friend rightly pointed out to me that there is a simpler way to write  line 
345. FOR n=0 TO 7 : s$ = HEX$ (k (n), 2). I did not know that you could format 
a HEX$ like that, although it IS in the manual... Did you?

9.3 Sound and OUT

Compared with the computers of the 1970s, Mallard BASIC allows you  tremendous 
sound  effects.  By PRINT CHR$(7), you can actually produce a BEEP!  For  most 
Amstrad  PCW  users  and programmers, that is quite  enough,  thank  you!  The 
Amstrad  PCW can, in fact, do a little more than this, although it  does  fall 
somewhat short of the effects that a well-run symphony orchestra can  produce. 
You  can produce different notes with differing tempos, but you will  have  to 
use machine code to do anything effective.

Mallard BASIC is capable of turning on sound and turning it off, but it is not 
fast enough to produce the frequencies by which you can vary a note to  taste. 
The  program below uses OUT 248,11 (Beeper on) and OUT 248,12 (Beeper off)  to 
produce  a slightly different note, but I don't think that you will  get  much 
more than this without using code.

        10 PRINT CHR$ (7)
        20 FOR n = 1 TO 100 : NEXT
        30 FOR m = 1 TO 20 : OUT 248, 11 : OUT 248, 12 : NEXT

Although  OUT  and INP are Mallard BASIC words, most applications will  be  of 
little  use without code. INP is generally used with peripherals not  supplied 
with  the computer (e.g. mice), and so is beyond the scope of this  book.  You 
will  find further use of OUT in the secont part of the book.  However,  there 
are  one  or  two effects of OUT that can be safely used  with  Mallard  BASIC 

OUT (like POKE), if used indiscriminately, will often cause a crash into limbo 
from  which  the only remedy is to switch off. However, there  are  some  OUTs 
which can be used safely, and may impress your friends.

OUT  246,n will reset the screen by n pixels vertically. Hence,  a  'circular' 
scroll  can be achieved. If a line disappears from the top, it will appear  at 
the bottom, and vice versa. For example:

        FOR n = 255 TO 0 STEP -1 : OUT 246, n : NEXT

OUT  247,n  produces a flash. If n is 0-63, it is an off, on flash.  If  n  is 
greater  than  127,  then you get an inverse flash. OUT  247,64-127  seems  to 
operate only if the inverse screen is installed.

OUT 248 does various things. The most useful are the sound values as above and 
the  screen off and screen on. Screen off OUT 248,8. Screen on OUT 248,7.  The 
one to watch is OUT 248,1. This has the same effect as [SHIFT] [EXTRA] [EXIT]. 
So, we end Part 1 of the book with:

        OUT 248, 1

Part 2

Chapter 10: A magical mystery tour

You  have just booked your ticket for our magical mystery tour of the  Amstrad 
PCW.  You  probably  bought  the Amstrad PCW as a  word  processor.  You  have 
realised that it is much more than a word processor. It is a computer that can 
be  programmed to do those dull and necessary tasks of life, such as  creating 
databases and invoicing. Probably, you have not yet realised just how powerful 
a computer the Amstrad PCW actually is. It has a lot of secret worlds that  we 
intend to explore.

Even on a mystery tour, you do have to make some preparations before you start 
out.  In this chapter, we shall discuss our mode of transport,  the  essential 
equipment, and an outline map of the countries to which we may be travelling.

10.1 The transport

Computers work with sequences of numbers but, for all practical purposes, mere 
humans have to work with a language that the computer can translate into these 
sequences  that  it  adores. Languages fall into  two  categories.  High-level 
languages are those that, to a greater or lesser extent, can be understood  as 
readable  English. Low-level languages are series of  instructions  (sometimes 
mnemonics) that are much closer to the language the computer understands.  The 
lower  the level of the language, the faster the computer will operate. It  is 
also  true that, in low-level languages, it is possible to make  the  computer 
stretch itself to the limit of its ability.

We shall continue to use Mallard BASIC as our high-level language -- our  main 
form  of  'transport'. Mallard BASIC is the bus which will be taking  us  from 
hotel to hotel but, just as would happen on a real tour, we shall have to  use 
more exotic transport to traverse the deserts, or scale the mountains. In this 
case,  it does mean that we shall have to use assembler or machine code,  from 
time to time. I don't think that you will find that you have to understand  Z-
80 code to follow where we are going, but you must not be frightened about it. 
Otherwise, you will miss some of our best day trips! There is nothing to  stop 
you  skipping any sections that you find too difficult, perhaps to  return  to 
them  later.  The code needed is all contained in the programs on  your  disc. 
Naturally,  if you want to develop our ideas, you will need some knowledge  of 
what is happening.

Even when we are using code, we will usually avoid hexadecimal. There are  two 
reasons  for this. The first is purely selfish -- unlike most people  who  use 
code, I prefer to think in decimal, as I can remember numbers better that way. 
The  second  is  that readers who prefer hexadecimal will easily  be  able  to 
convert from decimal, whereas the reverse may not be true.

10.2 Equipment

There  are just two items of equipment that I would advise readers to take  on 
this tour: a disassembler, and a multiple poke. Some people will prefer to use 
an assembler as well, although it is not essential. (ROCHE> It depends on  the 
size of your code. One subroutine can be hand-coded, but anything more complex 
needs to be written in a file, so be processed by an assembler.) You can  just 
about  write  in  Assembler by using SID and  various  other  rather  horrific 
sounding  facilities,  all of which are on side 3 of the master  discs.  These 
have  the  disadvantage that they were written for an earlier  processor,  the 
Intel 8080, and do not operate with all the Zilog Z-80 commands. (ROCHE> A  Z-
80  version of SID was made by Digital Research: ZSID.) If you want to use  an 
assembler  frequently, it is better to buy a more modern system of  your  own. 
The Hisoft discs are probably the ones most used by the professionals. (ROCHE> 
The  standard  Z-80  assembler  is  Microsoft's  M80  Version  3.44.)  At   DW 
Computronics, we have produced an assembler toolkit and tutorial that may well 
be easier for beginners. (ROCHE> I did not manage to find it.)

Assembler kits are an optional extra. All the examples in this book will  work 
from Mallard BASIC. Provided that you can copy in strings of figures, you  can 
work  without  an assembler. (ROCHE> Only if the code  is  relocatable...)  We 
shall  include  assembler  code examples (written  with  the  DW  Computronics 
assembler)  in  the  chapter  on Graphics, but  we  shall  always  include  an 
alternative coding which can be typed straight into Mallard BASIC.

Personally,  I  do  not usually use an assembler, but this  is  an  individual 
choice.  Most  people feel that it is essential for writing  their  own  code. 
(ROCHE> No: the most important tool is a good debugger, like ZSID.) However, I 
make a lot of use of a disassembler, and I think that most readers will  learn 
more  from these chapters if they sometimes use a disassembler.  (ROCHE>  This 
only shows the high-level mnemonics. A real debugger will show the contents of 
the flags and the registers as each instruction is executed. This is the  only 
way  to know what is going on, inside the Z-80 processor.) SID has  an  option 
that  will  give  you a disassembly but, as this only  recognises  Intel  8080 
codes,  it  is rather limited for our purposes. (ROCHE> Simply  use  the  Z-80 
version of SID: ZSID.) Program A5 is a disassembler (ROCHE> Sorry, but this is 
just showing the high-level mnemonics: this is a subset of any debugger, not a 
real  disassembler, producing complete source code files ready  to  assemble.) 
that  is  only slightly modified from the first program I ever  wrote  on  the 
Amstrad PCW!

I  am a strong advocate of the MULTIPLE POKE, although Mallard  BASIC,  rather 
surprisingly,  does  not  include  this  facility.  A  multiple  poke   allows 
successive bytes to be POKEd into memory with one command.

        POKE 128, 3, 4, 5

is equivalent to

        POKE 128, 3 : POKE 129, 4 : POKE 130, 5

This makes code routines shorter to write. It is over 12 times as fast as  the 
DATA-READ-POKE method. It also makes writing relocatable code easier.  Anyway, 
I am going to use it in this part of the book, whether you like it or not!  If 
you  use  Lightning BASIC or DWBAS, there is no problem. Appendix  4  contains 
routines for 1.29 and 1.39 which will give you this facility, although nothing 
else. This may occasionally be useful for construction of your own programs.

10.3 The map

We  are  going to assume that you have an Amstrad PCW8512 or PCW9512  for  the 
moment. If you have a PCW8256, all will be made plain at the end. Some  people 
find  it something of a mystery that, while you have a 512K computer,  loading 
Mallard BASIC gives you the message that you only have about 31K left!

The reason is that the Z-80, at any instant of time, can only work with 64K of 
RAM. (RAM stands for Random Access Memory, bytes of memory that can be read or 
written). The other 448K of memory is available, ready present and waiting for 
an  invitation  to come into the accessible memory. As a programmer,  you  can 
call any other part of the 448K into accessible memory but, while it is there, 
some of the normal memory will be waiting outside.

You can do this by a Mallard BASIC command: OUT x,y. This is not usually  much 
use,  as  interrupts will very soon change the memory back to normal.  Let  us 
assume  that interrupts don't exist, as it is easier to explain in terms of  a 
BASIC command. We shall use two terms, BANKS and BLOCKS, that are probably not 
in the official computer-speak language!

The 64K of accessible memory is divided into four BLOCKS of size 16K. The 512K 
of  physical  memory consists of thirty-two 16K BLOCKS. Each BANK  is  like  a 
socket,  and each BLOCK is like a pin. It is possible to put any pin into  any 

We shall refer to the BANKS by the first parameter that we would use in an OUT 
command. I/O port 240 refers to memory bytes 0-16383, port 241 to 16384-32767, 
port 242 to 32768-49151, and port 243 to 49152-65535.

BLOCKS  will  be  numbered 128 to 159. The usual numbering is  0-31,  but  the 
second parameter of OUT for these purposes is 128 to 159. When you are working 
in Mallard BASIC, blocks 132-135 will be the accessible ones, and they will be 
in  the I/O ports 240-243, respectively. Even in Mallard BASIC, there will  be 
time  when other blocks go into the accessible banks, but their visits are  so 
short that you will not be able to detect this.

We talked about an outline map. This is a description of what is contained  in 
each  of the blocks. Without going into much detail here, CP/M Plus refers  to 
the  general Operating System program that applies to the Amstrad PCW, and  to 
many  other computers using the Z-80 processor. The BIOS is the  program  that 
CP/M  Plus addresses to achieve the specific results for the Amstrad PCW.  The 
Operating  System  looks after the fundamental tasks, such  as  accessing  the 
screen, discs, and printer.

BLOCK 128 contains BIOS routines. BLOCK 129 contains some more, and also  part 
of the screen memory. BLOCK 130 contains the rest of the screen memory.  BLOCK 
131 contains other BIOS routines that are not screen-oriented.

In CP/M Plus, blocks 132-135 are known as the TPA ("Transient Program  Area"), 
where COM programs are loaded and run. It is probably more interesting to  see 
how they operate when BASIC.COM (which is a transient program) is run.

The  first  256 bytes of BLOCK 132 contain the CP/M Plus work area,  known  as 
"Page Zero". (ROCHE> Under CP/M, a "page" is 256 bytes long, which  correspond 
to   00H  to  0FFH  in  hexadecimal.  When  displaying  memory  locations   in 
hexadecimal,  each  time the location reaches 00H, you  have  reached  another 
page.  So, 0000H = Page Zero. 0100H = Page One (Start of the TPA).  So,  there 
can  be 00H to 0FFH, or 256, pages.) Mallard BASIC will sometimes  refer  here 
(ROCHE> When asking the BDOS of CP/M Plus to do some system calls.), but  this 
is  not  part of the interpreter. The rest of the block contains some  of  the 

The  rest  of  the Mallard BASIC interpreter fits into BLOCK  133.  This  also 
contains the start of the Mallard BASIC program area.

BLOCK 134 contains more Mallard BASIC program area.

BLOCK  135  is sometimes known as COMMON MEMORY. This block is  almost  always 
resident  in I/O port 243. 62982-65535 contains the CP/M Plus program  (ROCHE> 
Well, the "resident" portion, since CP/M Plus puts most of the BDOS in another 
bank.  So,  the resident portion of CP/M contains the minimum  to  communicate 
with  the other banks, where everything else is done.) The rest of  the  block 
can be used by Mallard BASIC programs (ROCHE> The bottom of this BLOCK,  since 
the  resident portion of CP/M Plus goes up to the top of the memory  (65535  = 
0FFFFH).),  although some of them will use part of it for other  purposes,  by 
the  MEMORY command (ROCHE> With MEMORY, you can allocate some of  the  memory 
below the resident CP/M Plus, for example to POKE long routines.).

BLOCK 136 contains the CCP program and the printer tables. When you see an  A> 
prompt  in CP/M Plus, the program in memory is the CCP program. It is  just  a 
program to do enough to load and run the COM program that you need.

BLOCKS 137-159 contain the M disc.

BANK 1 is the configuration normally present. The BLOCKS in the I/O ports 240-
243 are 132, 133, 134, and 135.

The other common configuration (during interrupts, for example) is called BNAK 
0.  This  consists of blocks 128, 129, 131, and 135.  For  screen  operations, 
block 130 replaces block 131.

On  the  Amstrad  PCW8256, the M disc will only  extend  from  block  137-143. 
However, if a block in the range 144-159 is requested, the result will be  the 
same  as  if the block 16 lower was called. This is a convenience as  you  can 
write  programs  with the code equivalent of OUT 242,159, which will  put  the 
last  block  of  M disc into I/O port 242 on ANY  Amstrad  PCW  computer.  OUT 
242,144  needs  greater caution. On the Amstrad PCW8512, this would  insert  a 
block  of memory disc but, on the Amstrad PCW8256, you would insert  the  main 
BIOS block... Handle this with extreme care!

Chapter 11: Graphics

In this chapter, we explore the world of high-resolution graphics. We have now 
reached  a point where we cannot do everything from Mallard BASIC alone.  Some 
machine  code is needed in our programs. In this chapter, we  shall  sometimes 
show it in assembler but, even when we do this, don't try to understand if you 
don't want to do so. Just use the programs that follow, and see what happens.

There  are  many graphics routines that could be written, but  they  will  all 
usually stem from one of a few fundamental ideas. Plotting a pixel, making new 
screen characters, saving to memory, dumping to a printer. Some of the  coding 
is  not difficult, once you know what is happening in blocks 129 and  130.  We 
start by having a look at how this part of the memory is organised.

11.1 Binary patterns

Skip this section if you understand what is meant by a binary pattern.

The screen is organised into 23040 sets of 8 pixels. Each pixel can be lit  or 
unlit.  If a pixel is lit, it will form a tiny dot on the screen.  From  these 
dots,  familiar  shapes  can be constructed. Each of the  23040  screen  bytes 
contains  a number 0-255, which describes a pattern of a horizontal row  of  8 
pixels.  Representing  pixels by * if lit and . if unlit, let  us  consider  a 
typical pattern:


The  left-hand  "*" represents 128 if lit, and 0 if unlit. This  is  sometimes 
known  as  BIT 7, since 128 = 2^7. (ROCHE> Computers, and  programmers,  count 
from  0, not 1. So, Bits 0 to 7 (from right to left...) for a byte (8  bits).) 
The next "*" has the value 64, the third from the left has value 32, and so on 
until the eighth "*", which has a value of 1.


        ********                      and      ........
        ││││││││                               ││││││││
        │││││││└──> Bit 0 =   1                │││││││└──> Bit 0 =   0
        ││││││└───> Bit 1 =   2                ││││││└───> Bit 1 =   0        
        │││││└────> Bit 2 =   4                │││││└────> Bit 2 =   0        
        ││││└─────> Bit 3 =   8                ││││└─────> Bit 3 =   0        
        │││└──────> Bit 4 =  16                │││└──────> Bit 4 =   0        
        ││└───────> Bit 5 =  32                ││└───────> Bit 5 =   0        
        │└────────> Bit 6 =  64                │└────────> Bit 6 =   0        
        └─────────> Bit 7 = 128                └─────────> Bit 7 =   0        
                            ---                                    ---        
                            255                                      0        

255 + 0 = 256 possible values for a byte. QED.)

The number that would represent this particular pattern will be:

        ││  │  │
        ││  │  └──>   1
        ││  └─────>   8
        │└────────>  64
        └─────────> 128

If 201 is expressed as a binary number, it will be 11001001 which, as you see, 
is exactly the same pattern as the pixels above.

11.2 The visible screen

When  blocks  numbers  129 and 130 are sent to I/O ports 241  and  242,  bytes 
22832-45871  contain  the  representation of all the  pixels  on  the  screen. 
Assuming  that the computer has just been switched on and the screen  has  not 
scrolled, bytes 22832-22839 give the pattern of the character at the top  left 
of the screen. (ROCHE> So, characters are 8 pixels tall.) Each byte contains a 
binary pattern representing a row of the character. (ROCHE> So, characters are 
8  pixels  wide.) For example, if the letter A was in the  top  corner,  these 
bytes  would contain 24, 60, 102, 102, 126, 102, 102, 0, respectively.  22840-
22847 represent the second column of the top row, and this continues until the 
row  is filled and bytes 22832-23551 have been used. 23552 would be the  start 
of  the second row down. If you do a few sums, you will see that, by the  time 
all 32 rows are filled, we have used bytes up to 45871.

Even  though  this  may  seem too simple, it is in  fact  likely  to  be  more 
complicated! We shall see how this happens in Section 11.4.

(ROCHE> So, letter A is made of the following binary patterns:

         24        ...**...
         60        ..****..
        102        .**..**.
        102        .**..**.
        126        .******.
        102        .**..**.
        102        .**..**.
          0        ........

11.3 The screen characters

Bytes 47104-49151 hold the character set that will be familiar to all of  you. 
Each screen character has an ASCII number, and each character will be held  in 
8 bytes in this table. For instance, A has the ASCII code 65, and  47104+65*8= 
47624. So, 47624-47631 hold the same bytes as 22832-22839 did when we imagined 
an A at the top of the screen.

11.4 Roller RAM

When  the  screen  rolls,  every character on the  screen  moves  into  a  new 
position.  You would imagine that each of the bytes 22832-45871 would need  to 
be  altered. This is not the case. The top line disappears and the new  bottom 
line is entered in its place, but the rest of this memory stays unaltered. The 
change takes place in the roller RAM table, which goes from 46592-47103.  This 
means that about 1000 bytes may have to be changed, instead of over 20.000. It 
makes sense, and gives smoother and quicker scrolling.

The  512 bytes in roller RAM contain 256 two-byte numbers which  indicate  the 
position  in memory of the start of each of the 256 rows of pixels  in  screen 
memory.  I have used the word INDICATE instead of GIVE since, for some  reason 
beyond  my  comprehension, the start of each line has still to  be  calculated 
from this indication.

Using a BASIC formula, if the two bytes in roller RAM are i, low byte, and  h, 
high byte, the line start is given by the formula:

        2 * (i + 256 * h) - (i AND 7)

NO, NO, NO. Please, save me. Honest... I did not design this computer!

As an unimportant but mildly puzzling point, you may well be asking about  the 
missing  bytes 45872-46591. I don't know! They are filled with zeros, and  the 
length is exactly a row of pixels. Could it be the status line when  disabled? 
Apparently  not. Could it have something to do with scrolling? This  does  not 
appear  to  affect it, although it may be used as a  temporary  dump.  Another 
faint  possibility is that it might be used in the [PTR][EXTRA]  screen  dump. 
Wouldn't it be dull, if we knew ALL the answers?

11.5 SCR_RUN

While  it  is possible to access screen memory by using OUT, it  is  generally 
easier  and shorter to use a BIOS routine named SCR_RUN. (ROCHE>  Actually,  a 
subroutine  of the Banked BIOS, not the Resident BIOS.) The result of  calling 
this routine is that I/O ports 240-242 are occupied by block numbers  128-130, 
which  include the screen environment. This call also has the  advantage  that 
interrupts are disabled and enabled where necessary, and the stack is moved to 
a safe position.

To  use SCR_RUN, it is essential that it is fed with a routine of your own  to 
run.  This  routine  must end with a RET, AND IT MUST BE  ENTIRELY  IN  COMMON 
MEMORY (48152-62981). (ROCHE> In hexadecimal: C000 to F605.) The coding  looks 
a  trifle odd -- to say the least! First, BC must be loaded with the start  of 
the  routine that you have written. You then CALL 64602 (ROCHE> FC5A, that  is 
to say: USERF, a Resident BIOS routine that Amstrad uses to access the  Banked 
BDOS  and BIOS.), which is the general call for any BIOS routine  (ROCHE>  No! 
There  is  a generic "Call BIOS" routine, function 50,  available  under  CP/M 
Plus.  This  one was customised by Amstrad to call the Banked BIOS.  Only  the 
Amstrad  PCW uses it.) This is followed by TWO bytes which are the address  in 
(ROCHE> banked...) BIOS where the routine starts. For SCR_RUN, the address  is 
233  (decimal) (ROCHE> E9: that is to say: the SCR_RUN routine in  the  banked 
BIOS.)  Hence, after CALL 64602, the next byte must be 233, and the  following 
one  0 (ROCHE> That is to say: 00E9. It is an INTEGER value.) If there  is  no 
further work to be done after returning from the CALL, you finish with another 
RET  (after the 233 and 0). When you return from SCR_RUN, you are back in  the 
normal environment, with the original stack. This means that it is possible to 
do  further operations in code before returning to Mallard BASIC or any  other 
main  routine that you are using. (ROCHE> The chapter "The  Implementation  of 
CP/M Plus on the Amstrad CPC6128 and PCW8256", written by Locomotive Software, 
and  published  as  Appendix  C of "The Amstrad  CP/M  Plus",  1986,  is  much 

11.6 A new screen clear

As  the  authors of '1066 and All That' would put it -- roller RAM is  a  Good 
Thing.  It  is  also a confounded nuisance when you  are  working  with  high-
resolution  graphics.  In  such a program, you will not  want  to  scroll  the 
screen.  Sometimes,  the  answer is to restore roller  RAM  to  normal  before 
working  in  high-resolution  graphics.  This makes  the  coding  quicker  and 
shorter.  This  routine clears the screen, and resets roller RAM at  the  same 
time. If it is included in a Mallard BASIC program, CALL j will do both  these 
jobs.  Here, the routine is written in Assembler (ROCHE> Well,  obviously  the 
famous  DW Computronics assembler, must be written in Mallard BASIC, since  it 
uses  an  ASC  file  produced by Mallard BASIC.  Note  also  the  non-standard 
mnemonics. And how do you insert new lines?):

        1 'LD A 27
        2 'CALL 1168
        3 'LD A 69
        4 'CALL 1168
        5 'LD BC START
        6 'CALL 64602
        7 'DW 233
        8 'LD A 27
        9 'CALL 1168
        10 'LD A 72
        11 'JP 1168
        12 '! START
        13 'LD HL 46592
        14 'LD DE 11416
        15 'LD B 32
        16 '! LOOP
        17 'PUSH BC
        18 'LD B 8
        19 'LD (HL) E
        20 'INC HL
        21 'LD (HL) D
        22 'INC DE
        23 'INC HL
        24 'DJNZ 249
        25 'EX DE HL
        26 'LD BC 352
        27 'ADD HL BC
        28 'EX DE HL
        29 'POP BC
        30 'DJNZ (LOOP)
        31 'RET

(ROCHE> In standard Z-80 code, this gives:


First of all, we empty the screen by the equivalent of PRINT CHR$(27)+"E". The 
Mallard BASIC interpreter contains a routine to print the value in register  A 
by a call at 1168. (ROCHE> That is to say: this code is not portable.) If  you 
are  not  using Mallard BASIC, an equivalent is LD C 2, LD E  value,  CALL  5, 
which  uses a BDOS routine (ROCHE> BDOS function 2: CONOUT.). The ! in line  8 
is  a  convention  to  signal  a label at this point  in  the  code  which  is 
interpreted  in  line 5. DW is followed by a number which is  entered  in  two 
bytes  into  the  code. Two bytes are often called a word, so  DW  stands  for 
Define  Word.  (ROCHE> A common assembler pseudo-op, since  1972...)  We  call 
SCR_RUN,  and the roller RAM is reset by some fairly standard code. After  the 
return  from  SCR_RUN, the cursor is sent to the top left. As  the  screen  is 
empty  during  the reset, nothing odd appears to happen! (ROCHE>  Again,  this 
explanation is not simple.)

        100 h = HIMEM : j = h - 56 : k = INT (j / 256) : i = j - k * 256 :  IF 
i > 227 THEN i = 227 : j = i + k * 256
        105 MEMORY j - 1
        110  POKE i, 62, 27, 205, 144, 4, 62, 69, 205, 144, 4, 1, i +  28,  k, 
205, 90, 252
        120 POKE j + 16, 233, 0, 62, 27, 205, 144, 4, 62, 72, 195, 144, 4, 33, 
0, 182, 17
        130 POKE j + 32, 152, 44, 6, 32, 197, 6, 8, 115, 35, 114, 19, 35,  16, 
249, 235, 1
        140 POKE j + 48, 96, 1, 9, 235, 193, 16, 237, 201

(ROCHE> In standard Z-80 code, this gives:


This is the same code in a Mallard BASIC routine that will place itself in the 
most economical position. The rest of your program can follow. It can  include 
CALL j, whenever you want a screen clear.

11.7 A little frivolity!

When we were at school, we all considered the possibility of making a multiple 
pen that would write many of our lines at the same time! I never saw one  that 
actually  worked.  This one does! Roller RAM is set so that each line  on  the 
screen  has  the same values in the roller. This means that, if you  write  it 
once, you write it 32 times.

        500 s = j : e$ = CHR$ (27)
        510 CALL s : h = HIMEM : MEMORY 51199!
        520 j = 51200! : POKE j, 1, 9, 200, 205, 90, 252, 233, 0, 201, 33,  0, 
182, 17, 152, 44, 6, 8, 115, 35, 114, 19, 35, 16, 249, 62, 184, 188, 200, 195, 
12, 200
        530 DATA I must, not, rite, wrude, words, about, my, teacher, on, the, 
lavatory, walls
        540  m = 400 : PRINT e$ "Y $"; : FOR n = 1 TO 12: READ a$ : PRINT  a$; 
" ";  :  CALL j : GOSUB 550 : NEXT : PRINT e$ "f"; : m = 4000 :  GOSUB  550  : 
PRINT e$ "Y "; : PRINT SPACE$ (90) : PRINT e$ "e"; : CALL s : MEMORY h : END
        550 FOR nn = 1 TO m : NEXT : RETURN

(ROCHE> This gives:


This  program  must  be appended to the previous one. (If you  have  DWBAS  or 
Lightning BASIC, you can avoid this by replacing CALL s with #c, as #c  resets 
roller  RAM.)  It is almost worth making a syntax error, as it will  be  quite 
spectacular!  You  can  get out of the mess and back  to  vague  normality  by 
pressing [RETURN] and then entering CALL s.

11.8 Plotting a pixel

The fundamental graphics routine is to plot a single pixel. Many readers  will 
have seen routines that do this, although probably not such a short one.  This 
one  does assume roller RAM to be reset, and so the calculations are  slightly 
less  severe. CALL j(x%,y%) will plot a single pixel x% pixels from the  left, 
and y% pixels from the top.

        1 'LD A (DE)
        2 'LD E (HL)
        3 'INC HL
        4 'LD D (HL)
        5 'LD HL 64816
        6 'ADD HL DE
        7 'RET C
        8 'LD BC START
        9 'CALL 64602
        10 'DW 233
        11 'RET
        12 '! START
        13 'LD C A
        14 'LD HL 22112
        15 'ADD HL DE
        16 'LD A L
        17 'AND 248
        18 'LD B A
        19 'LD A C
        20 'AND 7
        21 'OR B
        22 'LD L A
        23 'LD A C
        24 'RRA
        25 'RRA
        26 'RRA
        27 'AND 31
        28 'LD B A
        29 'INC B
        30 'LD A E
        31 'AND 7
        32 'LD DE 720
        33 'ADD HL DE
        34 'DJNZ 253
        35 'LD B A
        36 'INC B
        37 'XOR A
        38 'SCF
        39 'RRA
        40 'DJNZ 253
        41 'OR (HL)
        42 'LD (HL) A
        43 'RET

(ROCHE> In standard Z-80 code, this gives:


Lines  1-4 put y% into register A, and x% into register pair DE. The value  of 
x% is then checked but, as only the low byte of y% is needed, this will always 
be valid. SCR_RUN is then called.

The initial value of HL is the top of the screen - 720. This slight oddity  is 
so  that  register B must always contain a value greater than 0 in  the  first 
DJNZ loop. In lines 15-22, x% is added to register pair HL, to adjust for  the 
column. The lowest three bits are discarded, and replaced by the lowest  three 
bits of y%.

Lines  23-34  find the row number, and adjust register pair HL  for  this.  As 
register pair DE is used in this routine, the lowest three bits of register  E 
are saved in register A.

Lines 35-40 perform the operation A=2^A, so that A contains the binary pattern 
of the pixels to be set. The OR (HL) in line 41 could be replaced by XOR (HL), 
which would toggle the pixel. More usefully, line 41 could be replaced by  two 
lines: CPL, AND (HL), which would turn off the pixel.

        100 h = HIMEM : j = h - 60 : k = INT (j / 256) : i = j - k * 256 :  IF 
i > 236 THEN i = 236 : j = k * 256 + i
        110 MEMORY j - 1
        120  POKE j, 26, 94, 35, 86, 33, 48, 253, 25, 216, 1, i + 18, k,  205, 
90, 252, 233, 0, 201
        130  POKE j + 18, 79, 33, 96, 86, 25, 125, 230, 248, 71, 121, 230,  7, 
176, 111, 121
        140 POKE j + 33, 31, 31, 31, 230, 31, 71, 4, 123, 230, 7, 17, 208,  2, 
25, 16
        150 POKE j + 48, 253, 71, 4, 175, 55, 31, 16, 253, 182, 119, 201

(ROCHE> In standard Z-80 code, this gives:


This  is  the conversion of the code into Mallard BASIC. As you  can  see,  it 
normally uses just 60 bytes, although you will have to include the roller  RAM 
reset if your version of BASIC does not contain it.

11.9 Saving screens

Dr.  Logo for the Amstrad PCW allows you to save a screen as a  file.  Mallard 
BASIC does not. However, it contains a random file facility. Random files  are 
far  more  useful that most people realise. All we need to do is to  code  the 
screen  bytes into strings of length 128 bytes, then read them into  a  random 
file  using  PUT.  Later, this file can be read using  GET,  and  the  process 
reversed to load the screen that we have saved. The program below contains the 
two subroutines that we need.

        1000 f$ = "m:screen"
        1010 h = HIMEM : k = INT ((h + 1) / 256 - 1) : j = k * 256 : MEMORY  j 
- 1 : j = j + 128
        1020 POKE j, 94, 35, 86, 1, 140, k, 205, 90, 252, 233, 0, 201
        1030 POKE j + 12, 33, 0, k, 235, 1, 128, 0, 237, 176, 201
        1040 OPEN "r", 1, f$, 128 : FIELD 1, 128 AS a$
        1050 c$ = SPACE$ (128) : u = 22832 : FOR n = 1 TO 180
        1060 u% = UNT (u) : u = u + 128 : CALL j (u%) : v = VARPTR (c$) : POKE 
v + 1, 0, k
        1070 LSET a$ = c$ : PUT 1 : NEXT : CLOSE : MEMORY h : RETURN
        1090 :
        2000 f$ = "m:screen"
        2010 h = HIMEM : k = INT ((h + 1) / 256 - 1) : j = k * 256 : MEMORY  j 
- 1 : j = j + 128
        2020 POKE j, 94, 35, 86, 1, 140, k, 205, 90, 252, 233, 0, 201
        2030 POKE j + 12, 33, 0, k, 1, 128, 0, 237, 176, 201
        2040  m = j + 30 : POKE m, 35, 94, 35, 86, 33, 0, k, 235, 1,  128,  0, 
237, 176, 201
        2050 OPEN "r", 1, f$, 128 : FIELD 1, 128 AS a$
        2060 u = 22832 : FOR n = 1 TO 180 : GET 1
        2070 u% = UNT (u) : u = u + 128 : c$ = a$ : CALL m (c$) : CALL j (u%)
        2080 NEXT : CLOSE : MEMORY h : RETURN

(ROCHE> This gives:


The filename and disc chosen at the start of the subroutines at 1000 and  2000 
is  obviously your own choice. The routine at 1020 takes 128 screen bytes  and 
copies them into reserved memory. By using VARPTR, c$ is made to point to this 
string, which is then LSET to the file. The reverse process is similar. We use 
an  extra  piece  of code to place the GET string where we want  it.  This  is 
quicker than using ASC(MID$) and POKEs.

11.10 Altering the character set

This  is another fundamental screen routine. The program below is a  framework 
that  can be used to generate new screen characters. The routine is a  toggle, 
so  that,  when  the  program ends, the character set  can  be  restored.  The 
variables n and s are your choice. You can change up to 28 characters, but n+s 
must not exceed 255.

        9 REM n = Number of characters, s = Start of altered characters
        10 n = 2 : s = 200
        99  REM  There  should  be n lines of DATA,  consisting  of  8  binary 
        100 DATA 102, 102, 126, 102, 102, 60, 24, 0
        110 DATA 252, 204, 204, 124, 204, 204, 252, 0
        1000 GOSUB 50000 : CALL j
        1001 REM Rest of program
        1002 PRINT CHR$ (200), CHR$ (201)
        49999 CALL j : MEMORY h : END
        50000 r = n * 8 : v = 47104! + s * 8 : a = INT (v / 256) : b = v - a * 
        50010 h = HIMEM : k = INT ((h + 1) / 256 - 1) : j = k * 256 : MEMORY j 
- 1
        50020 FOR m = 1 TO r : READ x : POKE j + 31 + m, x : NEXT
        50030 POKE j, 1, 9, k, 205, 90, 252, 233, 0, 201
        50040 POKE j + 9, 33, b, a, 17, 32, k, 6, r, 26, 78, 119, 121, 18, 35, 
19, 16, 247, 201
        50050 RETURN

(ROCHE> This gives:


The  characters in the DATA lines 100 and 110 are eccentric versions of A  and 
B, as you will see when you run the program.

11.11 Further graphics

This  completes what I consider the fundamental routines. It leaves  you  many 
challenges.  Let us just consider one or two. Some attractive effects  can  be 
made by using a background of dark green by setting half the pixels. A  binary 
pattern of 85 or 170 in the part of the screen used will set alternate pixels.

Constructing a routine to draw a line can be done from Mallard BASIC with  our 
pixel plotting routine. An all-code routine will be much quicker. The  problem 
is  that  you have to consider which coordinate will be incremented  the  most 
quickly, which direction the line goes, and find coding which can be used with 
3-byte numbers, as you will need this amount of accuracy.

Circles  are  another  challenge.  I have found that the  best  method  is  to 
consider them as multi-sided polygons, and copy a rudimentaty form of the SINE 
tables  into memory. Fills are never easy if they are to cater  for  irregular 
shapes.  It may even be better to recourse to a program that is  partially  in 
high-level language.

You now have the tools -- use them well!

Chapter 12: Sound

At  the  end of this short chapter, you will doubtless come to  one  of  three 

     1. The Amstrad PCW is not very musical.
     2. You author is not very musical.
     3. Both.

There was a time, many moons ago, when I considered that I had almost  perfect 
pitch. I could play the first two movements of the "Moonlight Sonata" passably 
horribly. (Even the cat ran out when I tried the third.) I cannnot claim  this 
now -- perhaps it is the combination of background music and "Space  Invaders" 
that makes me appreciate that no music is infinitely preferable to bad music!

The  principle of producing notes from the beeper is quite simple  in  theory. 
You  turn it on and off many times. The pitch of the note depends on the  time 
interval  taken for the switch to operate. If you double this time,  the  note 
should be an octave lower. In practice, it is not quite so easy. If you try  a 
loop increasing the interval with each pass, successive notes should go  lower 
and  lower.  This  does  not always happen. I don't know why --  I  am  not  a 
physicist.  Probably, it is something to do with the natural frequency of  the 
beeper.  What  we need to find is a series of intervals throughout  which  the 
pitch does appear to be working in practice reasonably in accord with  theory. 
I  spent most of a morning thrying this, and that is quite enough! If you  are 
sufficiently interested to spend longer, you will probably do better.

As we showed in Chapter 9, it is possible to produce a slightly different note 
by  OUT  248,11 : OUT 248,12 in Mallard BASIC, but the problem with  BASIC  is 
that the time interval to execute even this line is much longer than the  sort 
of intervals that we need. (ROCHE> BASIC, including Mallard, is 10 to 50 times 
slower  than assembly.) Hence, it is necessary to invoke machine code for  the 
delay  loops between the 'ons' and 'offs'. The program that we will use  is  a 
good  example of the power of Mallard BASIC to harness code where needed,  and 
yet do most of the hard work itself.

12.1 If music be the food of love...

        1 GOSUB 200 : GOSUB 400 : t = 1
        10 m$ = "1h3a2f0r1h3a2f1rc2f0r1a2h1a2a#1r" : GOSUB 100
        11 m$ = "1g3a#2g0r1h3a2f0r1a2g1d2e1c4f" : GOSUB 100
        99 MEMORY h : END
        100 l = LEN (m$) : FOR n = 1 TO l : s = ASC (MID$ (m$, n)) : fl = 0
        105 IF n <> l THEN IF ASC (MID$ (m$, n + 1)) = 35 THEN fl = 1
        110 IF s = 48 THEN t = 0.5 : GOTO 160
        120 IF s > 48 AND s < 58 THEN t = s - 48 : GOTO 160
        130 s = s AND 223 : IF s = 82 THEN s = 67 : POKE i + 6, 12
        140 IF s < 64 OR s > 72 THEN 160
        150 k = c (s - 64) - fl : GOSUB 500 : POKE i + 28, a (k) : GOSUB 300
        160 POKE i + 6, 11 : NEXT : RETURN
        200 h = HIMEM : j = INT ((h + 1) / 256) - 1 : i = j * 256 : MEMORY i - 
        210 POKE i, 118, 243, 17, 100, 0, 62, 11, 205, 25, j
        211 POKE i + 10, 60, 205, 25, j, 27, 123, 178, 32, 242, 251, 201
        220 POKE i + 25, 211, 248, 6, 30, 14, 3, 13, 32, 253, 16, 249, 201
        230 RETURN
        300 CALL i : FOR m = 1 TO 50 : NEXT : RETURN
        400 DIM a (13), b (13), c (8)
        410 b = 2^(1 / 12) : a = 60 / b
        411 FOR n = 0 TO 13 : a (n) = ROUND (a) : b (n) = 3000 / a : a = a * b 
        420 DATA 4, 2, 13, 11, 9, 8, 6, 1
        430 FOR n = 1 TO 8 : READ c (n) : NEXT : RETURN
        500  f = ROUND (b (k) * t) : g = INT (f / 256) : POKE i + 3, f -  g  * 
256, g : RETURN

(ROCHE> This gives:


The  code is contained in the subroutine at 200. The delay loop (line 220)  is 
made  by counting down B and C, which are set to the right values  by  POKEing 
elsewhere. Lines 210 and 211 form the main loop, which switches on and off. DE 
is  set  as the repeat counter. Clearly, the shorter the  interval,  the  more 
times the loop must be executed to produce the same length of code.

The subroutine at 400 enters the values for the registers corresponding to the 
notes to be played. Intervals change on a logarithmic scale, and there are  12 
semitones  to  an octave. The interval must be increased by a  factor  of  the 
twelfth  root of 2 for every semitone lower. The data in line 420  corresponds 
to the letter positions of the musical notation, in the scale of C Major.  The 
subroutine at 500 is used for a double POKE of the correct values into DE.

The  subroutine  at  100 plays a musical string of notes  passed  by  m$.  The 
notation  is that the letters A-G (a-g) stand for the pitch of the note to  be 
played.  The  numbers  are  a signal for the length of  the  note.  1  is  the 
standard, and 3 is three times as long. Using 0 will give a note half as  long 
as the standard length. If no number appears before a note, the tempo will  be 
unchanged from the previous note. If no numbers are entered at all, the  tempo 
will  be  1. H or h is used for high C, so that a complete  octave  is  within 
range.  R  or r is used for a rest, the length is equal to the  current  tempo 
setting. # is used, following a note, to indicate that the sharp is  required. 
There  are no flats. However, you don't need them. B flat is exactly the  same 
as A sharp.

The  lines  before 100 contain the program. The two strings play what  (in  an 
optimist moment) I think you may recognise as the theme tune from  Beethoven's 
Pastoral symphony. The more musical readers will doubtless be able to  improve 

You  don't, of course, have to play notes on a musical scale to achieve  sound 
effects.  Try  some  yourselves -- if you live deep in the  country,  and  the 
resident owls are tone deaf!

WARNING:  I  have been told by a physicist that the  electronic  vibrator  (or 
whatever  it  is  called) may not be the same in all machines.  Hence,  it  is 
possible  that, if you try this program, the result will be totally  different 
on your computer!

Chapter 13: BASIC and CP/M

Unless you are interested in learning machine code, we suggest that you ignore 
this chapter at a first reading. 

In  the  introduction, readers were told that, if they did not want  to  write 
their  own programs in code, but just copy any routines given to  achieve  the 
same effect, that was fine. If that is your view, leave this chapter alone  -- 
come  back  later,  if you like! CP/M Plus is certainly part of  our  tour  to 
places where few Mallard BASIC programmers tread, and it has treasures for the 
low-level programmer, but -- to be honest -- not many of these treasures  will 
be  appreciated  by the programmer who wants to operate solely  in  high-level 

When you have loaded Mallard BASIC, you have two libraries of code subroutines 
at  your disposal, if you know where to find them. There are many routines  in 
the Mallard BASIC interpreter that can be called by your own program.  (ROCHE> 
Your  programs  become then version and CPU-dependent. Mallard BASIC  has  the 
VERSION  command, allowing programs to know under which CPU they are  running. 
But  you  then need to keep addresses valid for all the CPU  versions  Mallard 
BASIC  was  produced... It can be done but, fundamentally, it  is  simpler  to 
stick to high-level commands.) There are also the BDOS functions in CP/M Plus. 
Indeed,  the  Mallard  BASIC interpreter itself makes use  of  many  of  these 
functions.  In  this chapter, we shall first of all look at some of  the  more 
useful BDOS functions. There are about 70 of them, and it is beyond the  scope 
of  this book to detail all of them. If you want to program seriously in  CP/M 
Plus, fuller documentation is available. (ROCHE> "The Amstrad CP/M Plus",  MML 
Systems,  1989.) We shall, then, look at ways of making CP/M Plus and  Mallard 
BASIC interact.

To  avoid  having  to  tell you elsewhere,  the  following  abbreviations  are 
commonly  used -- so commonly that nobody actually tells you what  they  mean! 
(ROCHE>  Simply open the "CP/M Plus Programmer's Guide".) I think  that  these 
definitions  for abbreviations will give the idea -- even if they are not  the 
ones  the originators initially intended! (ROCHE> Why not follow the  official 

        CP/M -- Control Program for Microcomputers
        BDOS -- Basic Disc Operating System
        BIOS -- Basic Input/Output System
        DMA  -- Disc Management Area
                (ROCHE> ??? It means "Direct Memory Access"!)
        FCB  -- File Control Block
        PFCB -- Parameters for File Control Block
        DPB  -- Disc Parameter Block
        X*   -- Extended something or other!

13.1 Input and output

To use a BDOS function, it is essential to load register C with the number  of 
the function, and follow it by CALL 5. (ROCHE> Why not simply explain that, in 
high-level languages, you write CALL BDOS (Pstring, "Hello, World!") (or PRINT 
"Hello,  World!"),  while  in  assembler  you  fill  the  registers  with  the 
parameters, then call BDOS via its entry point at 0005H? So, LD DE,address  of 
string,  LD C,Pstring, CALL BDOS. Notice that the order of the elements of  an 
assembler  call is the reverse of a high-level language.) You may need to  set 
other  registers  in  certain  cases.  (ROCHE>  Simply  read  the  "CP/M  Plus 
Programmer's  Guide".) Usually, A and HL will contain some  information  about 
the  operation  when it is completed -- maybe simply error  information,  with 
which  we  will  not  concern ourselves, here.  Most  registers  will  not  be 
preserved,  so the coding must do the necessary PUSHing and POPping.  In  this 
section,  we  shall  look at BDOS system calls 0, 1, 2, 5, 9,  and  10,  which 
include the main input and output functions.

Function 0 (ROCHE> "System Reset".) is the way a CP/M Plus program returns  to 
the A> prompt. It is the system reset. There are several ways of ending a CP/M 
Plus program, but all of them, eventually, call this function. You can LD C  0 
and JP or CALL 5 (ROCHE> 0 is the number of the "System Reset" function of the 
BDOS, and 5 (0005H) is the entry point in memory of the BDOS.). JP 0 takes you 
to the same address (ROCHE> 0 (0000H) is the entry point of the BIOS, but  the 
first BIOS function happens to do the "System Reset" for the BDOS...). So does 
a  RET,  if  the  stack is managed correctly (ROCHE> It is  safer  to  use  LD 
C,SysReset; CALL BDOS.). A RST 0 also works (ROCHE> Since Geoffrey Childs  did 
not  explain  the  916  opcodes  of the Z-80  CPU  in  general,  and  the  RST 
instructions  in  particular, only experienced assembly  language  programmers 
will  understand this remark. RST ("restart") instructions are  one-byte  long 
calls, but to special addresses in the "Page Zero".)

Function 1 (ROCHE> "Console Input".) inputs a key (ROCHE> More precisely,  the 
character  outputted  by  a key of the keyboard.). It waits  until  a  key  is 
pressed. If you want the ASCII value, it is returned in register A. Function 2 
(ROCHE>  "Console Output".) is a single key output. Register E is loaded  with 
the  ASCII value, and the character is printed on the screen. Note  that  this 
function (and function 9) do not produce an automatic Carriage Return  (ROCHE> 
And  a   Linefeed, that is to say: starting a new line  after  outputting  the 
character/string.). If you want this, you must output ASCII 10 and 13 as  well 
(ROCHE>  10  =  Carriage Return, 13 = Linefeed.).  Function  5  (ROCHE>  "List 
Output". Here, "list" means "printer".) is similar to function 2, except  that 
the output goes to the printer.

While any string of characters can be printed on the screen by successive  use 
of  function  2, it is easier to use function 9 (ROCHE> "Print  String".).  In 
this function, DE is set to the address in memory where the string begins. The 
string can contain normal ASCII characters, including control codes, but  must 
not contain ASCII 36 (the $ sign), except at the end. This is the signal  that 
the string has terminated. Function 10 (ROCHE> "Read Console Buffer".)  inputs 
a string. We shall return to this function in a moment.

13.2 DMA and FCB

When  using  CP/M Plus to operate on files, there are two main data  areas  to 
consider.  The DMA is a buffer of 128 bytes, which is a general workspace  for 
the  routines.  The  FCB is an area of (up to) 36 bytes,  which  contains  the 
information  about the file being used. It is usually convenient to  have  the 
DMA  at 128-255 and the FCB at 92-127. If you are working from Mallard  BASIC, 
the  relevant addresses may be different, so it is always wise to set the  DMA 
first.  This  is done by setting DE to the start of the DMA,  and  using  BDOS 
function 26 (ROCHE> "Set DMA Address".).

BDOS 10, the string input function, will normally use the DMA. DE is set to  0 
to signal this (ROCHE> No: 0 means "use the DMA buffer", no matter where is is 
located in memory.). Before the function is called, it is necessary to set the 
first byte of DMA to the maximum length of the intput string, which should NOT 
exceed  126. The third byte should be set to 0. This is where the string  will 
start  when  it  has been stored in memory. The second byte of  the  DMA  will 
contain  the length of the string after the operation is complete. The  string 
input  works  like  INPUT  in Mallard BASIC.  Press  [RETURN]  when  you  have 
finished.  As  this function is considerably more complicated than  the  other 
input  and  output functions, we give an illustrative program.  (ROCHE>  Well, 
everything is complicated, if you don't quote the "Programmer's Guide"!)

        10 v = 51200! : MEMORY v - 1
        20 POKE v, 17, 128, 0, 213, 14, 26, 205, 5, 0, 225
        30 POKE v + 10, 54, 126, 35, 35, 54, 0, 17, 0, 0, 14, 10, 195, 5, 0
        40 CALL v
        50  PRINT : m = 130 : FOR n = 1 TO PEEK (129) : p = PEEK (m)  :  PRINT 
CHR$ (p); : m = m + 1 : NEXT

(ROCHE> This gives:


The code works like this:

Set DMA address, and save: LD DE 128, PUSH DE, LD C 26, CALL 5
Put  126 and 0 in first and third bytes of DMA: POP HL, LD (HL) 126,  INC  HL, 
Call the input string function, and return: LD DE 0, LD C 10, JP 5

I think that the best way to deal with FCBs is not to bother with details, but 
to  show how an FCB is easily created by BDOS 152 (ROCHE> "Parse  Filename".). 
This  takes  a  filename string, and parses it into the  correct  syntax.  The 
filename  can  included disc name followed by a colon (":"),  it  can  include 
wildcars ("?" and "*"), and you can end it with a semicolon (";") followed  by 
a  password.  The filename string should end with a zero when it is  put  into 

For  this BDOS call, we need to create an extra 4-byte area called  the  PFCB. 
This  contains the address of the filename string, followed by the address  of 
the FCB. DE is loaded with the PFCB address before the function is called. The 
program below illustrates this.

        10 v = 51200! : MEMORY v - 1
        20 INPUT "Enter filename: ", f$ : f$ = f$ + CHR$ (0) : l = LEN (f$)
        30  FOR  n = 1 TO l : POKE 51299! + n, ASC (MID$ (f$, n))  :  NEXT   ' 
String at 51300
        40 POKE 51250!, 100, 200, 92, 0  ' This is the PFCB
        50 POKE v, 17, 50, 200, 14, 152, 195, 5, 0 : CALL v
        60 FOR n = 92 TO 127 : PRINT PEEK (n); : NEXT

(ROCHE> This gives:


13.3 Reading and writing files

Once the two ideas (FCB and DMA) are understood, reading and writing files are 
not  difficult.  All the new functions introduced in this section need  DE  to 
point to the start of the FCB, and it is wise to ensure that the DMA is set to 
start  at  128.  To read a file into memory, first make  the  FCB  using  BDOS 
function 152. You must not use wildcards in this application.

Now,  open  the  file  (BDOS function 15  (ROCHE>  "Open  File".)),  and  read 
sequential  (BDOS  function 20 (ROCHE> "Read Sequential".). The  first  record 
will  be in the DMA, so use an LDIR to put it into memory where  you  require. 
The  FCB will point to the next record, so the process can be  repeated  until 
the file is complete. Close the file by using BDOS function 16 (ROCHE>  "Close 

To write a file, set up the FCB and start with function 22, "Make File".  This 
will open the file (ROCHE> No: it will put the filename in the directory,  but 
not a single byte in the file.). To write to the file, you must load the  data 
into the DMA before calling "Write Sequential" (BDOS function 21). Once again, 
remember to close the file (BDOS function 16).

13.4 The file directory

An FCB can also be used to obtain directory details of a disc. DE should point 
to the FCB for both the functions below. BDOS function 17 (ROCHE> "Search  For 
First".) searches the disc for the first matching file. If this is followed by 
BDOS  function 18 (ROCHE> "Search For Next".), the next match will  be  found. 
BDOS function 18 can be repeated. In this case, the return in A from the  call 
is  of  importance. A result of 255 means that no file (or further  file)  has 
been  found. If A contains 0-3, this tells us where the details are stored  in 
the DMA area. If the DMA start if 128, 0 means 128-159, 1 means 160-191, etc.

The program below gives a full directory if a filename *.* is entered. It  can 
also  give  a  partial directory by putting only some of  the  filename  as  a 
wildcard. Entering *.BAS will list only Mallard BASIC files. The record  count 
is only accurate as LOF in Mallard BASIC. It would need a more complex program 
to give a correct reading for files over 128 records long (16K). The lines  up 
to 50 are the same as the previous program, apart from the omitted REMs.

        10 v = 51200! : MEMORY v - 1
        20 INPUT "Enter filename: ", f$ : f$ = f$ + CHR$ (0) : l = LEN (f$)
        30  FOR  n = 1 TO l : POKE 51299! + n, ASC (MID$ (f$, n))  :  NEXT   ' 
String at 51300
        40 POKE 51250!, 100, 200, 92, 0  ' This is the PFCB
        50 POKE v, 17, 50, 200, 14, 152, 195, 5, 0 : CALL v
        60 POKE v, 17, 128, 0, 14, 26, 195, 5, 0 : CALL v
        70 POKE v, 17, 92, 0, 14, 17, 205, 5, 0, 50, 40, 200, 201
        80 CALL v : GOSUB 100 : POKE 51204!, 18
        90 CALL v : GOSUB 100 : GOTO 90
        100 x = PEEK (51240!) : IF x = 255 THEN PRINT : END
        110  y = 129 + x * 32 : FOR n = y TO y + 7 : PRINT CHR$ (PEEK (n));  : 
        120 FOR n = y + 8 TO y + 10 : PRINT CHR$ (PEEK (n)); : NEXT
        130 PRINT USING "###"; PEEK (y + 14); : PRINT " Records.", : RETURN

(ROCHE> This gives:


13.5 Other BDOS calls

There  are  just  three  other BDOS functions that I  want  to  mention  here, 
coincidentally  numbered 45, 46, and 47. Function 45 (ROCHE> "Set  BDOS  Error 
Mode".)  sets the error mode. If you are a Mallard BASIC programmer, you  will 
doubtless have suffered the irritation of being dumped unceremoniously in CP/M 
Plus  after some disc error. Murphy's law makes it virtually certain that  you 
had  not saved your program for at least an hour! There are 3 different  error 
modes  that  can  be set by loading E with 0, 254, or 255,  and  calling  this 
function. If you set E to 254 and call it from Mallard BASIC, your disc errors 
produce  the usual gobble-de-gook, but then return you to Mallard BASIC  (with 
luck)!  This appears to be a considerable improvement. It may be simpler  just 
to POKE (see Section 14.2).

BDOS  function 46 (ROCHE> "Get Disk Free Space".) gives the disc  free  space. 
Set register E to the relevant drive: 0 for A, 1 for B, and 12 for M. Call the 
BDOS  function, and then PEEK the first three bytes of the DMA. (PEEK (128)  + 
256  *  PEEK  (129 + 65536 * PEEK (130)) / 8 should give  the  free  space  in 

Function  47 (ROCHE> "Chain to Program".) chains a program. Register E  should 
be set to 255. The new program name should be POKEd from 128 onwards, just  as 
you would write it in, after the A> prompt. You must finish the string with  a 
zero byte.

13.6 Writing CP/M files

Mallard  BASIC  can be used to produce a COM program, one that will  run  from 
CP/M  Plus  without Mallard BASIC being loaded. The  technique  is  reasonably 
simple.  The  code is POKEd into memory, which is then transferred  to  string 
variables,  and  saved in a random file, which must be named with COM  as  the 

The file must be written so that it will start at location 256 (ROCHE>  0100H, 
the start of the TPA.) when it is eventually run and, naturally, all CALLs and 
JPs  must refer to the eventual location, rather than the location  where  the 
code is actually written. A convenient way that I usually use is to set MEMORY 
40255, and start POKEing at 40256. This means that all address references have 
a displacement of 40.000. A simple example will show the technique.

        10 MEMORY 40255! : FOR n = 40256! TO 40383! : POKE n, 0 : NEXT
        20 a$ = "This is my first CP/M program." + "$" : l = LEN (a$)
        30 FOR n = 1 TO l : POKE 40299! + n, ASC (MID$ (a$, n)) : NEXT
        40 POKE 40256!, 17, 44, 1, 14, 9, 205, 5, 0, 195, 0, 0
        50 FOR n = 0 TO 127 : b$ = b$ + CHR$ (PEEK (40256! + n)) : NEXT
        60 OPEN "r", 1, "myprog.com", 128 : FIELD 1, 128 AS c$
        70 LSET c$ = b$ : PUT 1 : CLOSE

13.7 Combining BASIC and CP/M Plus

Let us list the combinations of programs that you might want:

     1. Run a CP/M Plus program, then a Mallard BASIC one.

     2. Run a Mallard BASIC program, and chain a CP/M Plus program.

     3. CP/M Plus, then Mallard BASIC, then CP/M Plus.

     4.  Mallard  BASIC,  then  CP/M Plus, and return  to  the  Mallard  BASIC 

1. One method for this is well known. Use a PROFILE.SUB file, and include  the 
CP/M Plus program, followed by BASIC BASPROG. An alternative method is to  end 
the CP/M Plus program with BDOS function 47, the chain function. The CP/M Plus 
program must put BASIC BASPROG (followed by a zero byte) starting at  location 

2. A similar method can be used, here. This time, the command line is POKEd in 
to  the correct locations, and then BDOS function 47 ("Chain to  Program")  is 
called from Mallard BASIC.

3. This needs a combination of the methods in 1 and 2.

4.  And  this  is  the $64.000 question! It certainly  can  be  done  in  some 
circumstances. In the latest version of Lightning BASIC Plus, this facility is 
offered, but I have refused to guarantee it in all cases. My advice to you  is 
not  to  bother with the end of this section -- unless you want  to  share  my 

The  technique  is to save blocks 132-133-135 on the M disc,  save  the  Stack 
Pointer in block 135, reset the stack in this block, and alter the JP  address 
at  0  to jump to the new routine. (On the first use, it must  do  the  system 
reset  which  is part of BDOS function 47, "Chain to Program". On  the  return 
from  the CP/M Plus program, it must do something quite different.)  The  CP/M 
Plus program is then chained in the normal way. On return, blocks 132 and  133 
are retrieved from the M disc, the original Stack Pointer is recalled, the old 
block  135  is  restored, and a simple RET restores  everything  back  to  the 
original position in the Mallard BASIC program.

The  problems arise with the CP/M Plus program. If this uses the same part  of 
the  M  disc,  we are in trouble. DISCKIT is an example.  If  it  uses  memory 
between  32768  and  the  end  of our  routine,  something  will  probably  be 
corrupted. However, the most usual problem is repairable.

In  this book, I have used CALL 64602 for SCR_RUN and other BIOS  calls.  This 
works with all Amstrad PCWs. However, to cater for different versions of  CP/M 
Plus,  there  is  a 'safer' method. The address is  calculated  from  what  is 
contained at location 1 added to 87 (ROCHE> This is the method recommended  by 
Locomotive  Software.).  The coding LD (HL) 1, LD DE 87, ADD  HL  DE,...  will 
often  be  seen. There are two ways round the problem. The first is  to  patch 
every  CP/M Plus program containing this. Replace LD (HL) 1 with LD HL  64515. 
The other method is to ensure that, 87 above the new start address in location 
1, there is a JP 64602.

This  does  not  quite solve the problem, as one or  two  CP/M  Plus  programs 
calculate  other addresses from the system reset start. I have not  found  any 
alternative  to  patching  the CP/M Plus program. That is as  far  as  I  have 
reached with this question. Perhaps you can think of something simpler or more 
effective! (ROCHE> Obvious: Read the CP/M Plus manuals! Use an RSX. ("Resident 
System Extension"). See the manuals for more information about RSXs.)

Chapter 14: Useful addresses...

(and probably one or two useless ones, as well!)

In  this  chapter,  we  look at the Mallard  BASIC  interpreter,  and  discuss 
addresses of routines or tables that may be of use or of interest.  Obviously, 
this  is  far from a complete disassembly. Readers may well have  found  other 
useful  addresses.  If a number is followed by square brackets  --  e.g.  3753 
[3752]  for  Syntax  Error -- the bracketed address refers  to  Mallard  BASIC 
Version 1.39. Where no brackets follow, the addresses are the same in 1.29 and 
1.39. We shall list addresses in numerical order, not in order of  importance. 
The  second section of this chapter will contain some additional addresses  in 
CP/M Plus that can be used by Mallard BASIC.

14.1 The Mallard interpreter

256.  The  start  of the Mallard BASIC interpreter. Jumps  to  a  housekeeping 
routine  at 724. If this jump is changed to 406, a warm start can be  obtained 
in certain circumstances.

259-262.  Addresses  of  routines used with  USR.  (ROCHE>  "Get_Integer"  and 

263-405. Start up messages. See also 22466. From about 300 onwards, this  area 
can be used safely to give space for code routines that can be written without 
requiring a reset of HIMEM.

288. Normally set to 0 as the end of part of the message. It is also used as a 
signal in editing. If set to a non-zero value, all unnecessary spaces will  be 
edited out of the listing of a program.

406-.  Warm  start.  This can be called to end a  program  without  any  error 
messages.  It  resets  the  stack, so it can be used to jump  out  of  a  code 
routine.  A  slight amendment of the code will produce a line for  editing  in 
errors other than Syntax error.

450.  Produces a line for EDITing. HL should be set with the line  number.  If 
the  line does not exist in the program in memory, an error will  occur.  This 
routine combines with AUTO (one interception is at 471), and it is possible to 
amend it for variations to the AUTO operation.

724. This sets up the default parameters for the system. The various  routines 
called could be amended, so that (for example) the default LPRINT width  could 
be changed.

964.  If  register A is set to 3 and this subroutine is called in  a  printing 
routine,  the  output goes to the printer and not to the screen.  The  default 
value  is 3 for screen printing, and this is reset into 28446 [28612]  at  the 
end of each Mallard BASIC statement.

1018. Waits for a keypress, and returns the result in register A. See 1024.

1024.  Returns the latest keypress in register A. If the routine has not  been 
called  since a keypress was last made, this value is returned, even if a  key 
is not actually being pressed at the time of the call. 0 is returned if  there 
is no relevant keypress.

1125. Prints characters from HL to the screen. HL is set to point to a  string 
which  is  terminated  by  0 as a signal to end. This can  be  used  with  the 
routined at 964 to divert to the printer.

1168 (or 1144). Prints a single character contained in register A.

Following  the  PRINT routine, there are a few routines  for  setting  special 
situations  (e.g.  WIDTH). This is followed by the program  control  routines: 
GOSUB-RETURN, FOR-NEXT, and WHILE-WEND. We shall not detail where all  Mallard 
BASIC  word routines are to be found, but we will show you that it is not  too 
difficult to find a particular routine that you want to examine.

At  3753 [3752] comes the first of the main addresses to call if you  want  to 
produce  an  error situation. There is no reason why a machine  code  routine, 
called  from  Mallard BASIC, should not use the error codes  already  present. 
3753  [3752]  gives Syntax Error, 3757 [3756] Improper Argument,  3763  [3762] 
gives  the error corresponding to the value in register A when the routine  is 

4022 [4021]. "Break in XX" routine.

4306. This is rather a fascinating routine giving error messages. A new 'ASCII 
type'  code is built up for values over 128. These codes contain  combinations 
of  letters that build up part words and complete words used in the  messages. 
If you wish to write programs that condense files by hashing, it is well worth 
a study of how Mallard BASIC does this. A CALL to 4306 with DE pointing to the 
string to be printed demonstrates this. 0 is the terminator of the string, and 
this  little  program shows the build up of the words from characters  in  the 
128-223 range:

        10 MEMORY 51199! : POKE 51200!, 17, 0, 201, 205, 210, 16, 201
        20 m = 51456! : FOR n = 33 TO 223 : POKE m, n, 95 : m = m + 2 : NEXT : 
POKE m, 0
        30 v = 51200! : CALL v : END

(ROCHE> Indented, this gives:

        10 MEMORY 51199!
        20 POKE 51200!, 17, 0, 201, 205, 210, 16, 201
        30 m = 51456!
        40 FOR n = 33 TO 223
        50     POKE m, n, 95
        60     m = m + 2
        70 NEXT
        80 POKE m, 0
        90 v = 51200! : CALL v
        100 END

(ROCHE> In standard Z-80 code, this gives:


We  now  come  to the expression calculating routines. For all  of  these,  HL 
points to the start of the expression AS IT IS IN A MALLARD BASIC PROGRAM, and 
not  in  ASCII form. You can experiment to see how numbers  are  contained  in 
memory by writing a first line of a program, and PEEKing from 31382 [31523] to 
see  the changes from the ASCII that appears on the screen. For  instance,  in 
the  line: 1 n = 2.5 + 3.4, the "2.5" would be represented by 30 0 0  64  131, 
the 30 being a signal for a single precision Floating-Point variable, and  the 
following 4 bytes will evaluate to 2.5.

4931. Evaluates to a whole number 0-255, and leaves the answer in register  A. 
A  decimal  result will be rounded, but a number outside the  range  gives  an 
error. This is one of the few routines that differs in Mallard BASIC 1.39.  In 
1.29, the result is echoed in register E, which is sometimes useful. In  1.39, 
the register DE is preserved if the routine is called.

4944. As above, but a 0 answer gives an error.

4977 [4974]. This time, the value can be in the range 0-65535, and the  result 
will be contained in DE.

5106  [5103].  This  is  the most general routine,  and  the  result  will  be 
contained at 29778 [29919] and the bytes following. The location 29777 [29918] 
contains a number which gives the expexted type of variable. This routine will 
achieve string arithmetic, as well as numeric. In this case, 29778 [29919] and 
29779 [29920] will give a pointer to the 'VARPTR' of the result.

5724 [5721]. The function address table. This is rather more complex than  the 
command  address table, which we will encounter later. A command is  generally 
coded  in Mallard BASIC into a single number above 128. For example, PRINT  is 
coded as 179. A function is coded into two numbers: the first is 255, and  the 
second is the number of the function. The functions are numbered 1-46, and 99-
127. The second group comes first in the table. So, the address given by  PEEK 
(5724  [5721]  + 256 * PEEK (5725 [5722]) would give you the  address  of  the 
routine for function 99 (CONSOLIDATE). Function 1 (ABS) will have its  address 
in  5784-5785  [5781-5782]. Many functions will already have  their  arguments 
contained in register HL by the time they arrive at their address.

8279  [8377]  onwards. Investigation in this region  shows  the  communication 
between  Mallard BASIC and CP/M Plus. When calls are made here, error  control 
passes to the CP/M Plus system. This is why you can suddenly receive a  nearly 
incomprehensible message and a return to the A> prompt. These routines all use 
a BDOS call.

8356-8357  [8454-8455].  In CP/M Plus, USER allows you to change  the  default 
group on the disc. If you then enter Mallard BASIC, the group is changed  back 
to  group  0 (ROCHE> ???). You can do most disc operations in  another  group. 
SAVE"2:MYPROG" would save in group 2, for example. These two locations can  be 
changed  to alter the DEFAULT group, and subsequent operations will all go  to 
the  new  default group, just like USER in CP/M Plus. They  contain  25,  119. 
Replace by 62 and the number of the new default group you require. Recently, I 
discovered  that an easier method is: OPTION FILES "2" (ROCHE> This  paragraph 
is  very confusing, because there is no explanation of the difference  between 
the  default  user number under which Mallard BASIC was loaded, and  the  user 
number under which its files are saved/loaded. OPTION FILES change the  later, 
not  the user number where Mallard BASIC was loaded, which will still be  same 
after  exiting  Mallard  BASIC.  See the "CP/M Plus  User's  Guide"  for  more 
information about the USER command.)

8783-8797  [29155-29166]. This is a jump table for direct entry to CP/M  Plus. 
PRINT CHARACTER IN REGISTER C, LPRINT DITTO. The 1.39 table only contains  the 
last four jumps. System Reset is dealt with slightly differently.

We  now  make  a big jump. Much of the intermediate area  is  devoted  to  the 
function  routines and, if your idea of a happy afternoon is finding  out  the 
algorithm  for calculating COS, I am sure that you will satisfy  your  deepest 
desires by finding where COS resides from the function table!

15750  [15816].  HL points to a variable in a Mallard BASIC  program.  If  the 
routine is called, then on exit DE will contain VARPTR for that variable. This 
may  not  seem  terribly  useful, but it is a vital  routine  for  adding  new 
keywords to Mallard BASIC.

17240 [17311]. Input prompt character.

18165  [18213].  A useful routine, which checks whether HL does point  to  the 
character expected. If so, HL is incremented; otherwise, an error occurs.  The 
format  is to make the call, and the following byte is the ASCII code  of  the 
expected  character. For those of you who are learning Z-80 code, the  routine 
is an interesting (although fairly standard) use of the Stack Pointer.

18425 [18489] onwards. The address table for the commands. The first two bytes 
give the address of AUTO, which is represented by 128. The next two give  DATA 
(129), etc.

18599  [18679]. This routine is used to convert a series of  ASCII  characters 
into the form normally used in Mallard BASIC program storage. The string  will 
usually be in the buffer, starting at 28466 [28632].

19188  [19289]. This is the start of the LIST routine, which consists of  four 
CALLs and a JP 406. If PUSH HL is inserted between the first and second  calls 
and the JP 406 is replaced by POP HL and RET, it is permissible to use LIST as 
a  program line. This obviously has a use in demonstration programs. Since  it 
uses  no more space, it is rather surprising that the code was not  originally 
written in this way.

19820  [19966]. This forms the table of the reserved words allowed by  Mallard 
BASIC.  In the first Part of the book (Section 8.1), there is a  program  from 
which the construction of the table and the contents can be studied.

21020 [21198]. This will search a Mallard BASIC program for a particular  line 
number.  DE  contains  the number, and HL contains the  address  at  which  it 
resides in memory. The Carry flag is reset if no such line exists.

22466  [22644]. This contains the "Acorn Computers" message (in  less  obvious 
form  in 1.39). Together with the beginning of the start up message, it  forms 
an encrypting technique for protected Mallard BASIC programs. Each byte of the 
program is XORed with one byte from each message, and the result is saved. The 
XOR  method of encryption is a useful trick, as the routine works as a  toggle 
to restore to the original characters.

22540  [22711].  The start of the routine which checks whether  a  program  is 
protected, and signals an error if various Mallard BASIC words are used in the 
protection  racket.  By  POKEing 22540 with 201  before  loading  a  protected 
Mallard  BASIC  program, the routine can be bypassed. Subsequently,  it  is  a 
simple matter to reset the protect flag at 29628 [29769].

23087 [23258]. A useful routine that prints the value of HL on the screen.  It 
can  be used quite safely to demonstrate CALL. This routine uses this and  the 
CALL at 21020 to find where a Mallard BASIC program line begins in memory.

        1000 INPUT "Enter line number: ", i : i% = UNT (i)
        1010 h = HIMEM : v = h - 10 : MEMORy v - 1
        1020 a = 28 : b = 47 : IF PEEK (5103) = 197 THEN a = 206 : b = 128
        1030 POKE v, 94, 35, 86, 205, a, 82, 195, b, 90 : CALL v (i%) : MEMORY 

(ROCHE> This gives:


24164  [24330].  This is the PRINT routine. It has uses in  code  routines  as 
Mallard  BASIC  variables  can be printed by pointing HL to  the  variable  in 
memory and CALLing this address.

BASIC.COM  is  a 28K program. Yet, the interpreter takes up 31K. The  last  3K 
(roughly)  is used for a stack, flags, workspace, and buffers. We will have  a 
look at some of the more interesting parts of this area.

28117  [28224]. This is the AUTO information. There is a flag followed by  the 
increment and the current line number.

28446 [28612] is the location below which the Mallard BASIC stack is made. Any 
error  will  restart  the stack at this point. A curiosity  is  that,  if  you 
carelessly  load  in  the screen RAM into area 241, you may  see  little  dots 
forming on the screen while a code routine does the stacking!

28446  [28612]  also  starts  a parameter area of 20 bytes  which  may  be  of 

28446 [28612]. Current output device: 3=screen, 2=printer.

28447 [28613]. Flag to some keyboard routines. Wait for keypress?

28448 [28614]. Hold last unused keypress (ASCII).

28449 [28615]. OPTION RUN/STOP flag.

28450 [28616]. OPTION TAB/NO TAB flag.

28451-28453   [28617-28619].  Width  of  screen  (wordwrap,  and  ZONE   limit 

28454 [28620]. Current PRINT position on line.

28455 [28621]. Current LPRINT position on line.

28456 [28622]. WIDTH LPRINT.

28457 [28623]. FOR-NEXT nesting flag.

28458-28461 [28624-28627]. Single precision variable at start of FOR-NEXT.

28462-28463 [28628-28629]. Address of start of FOR-NEXT loop.

28464-28465 [28630-28631]. Address of start of WHILE-WEND loop.

28466 [28632] is the buffer into which you write when you type a Mallard BASIC 
line  on the screen. The contents are held in ASCII until the [RETURN] key  is 
pressed. The line is partially compiled into a workspace area (see below), and 
then entered into the current program.

29707-29708 [29848-29849] contains the current value of HIMEM.

29717  [29858] begins a list of addresses regarding the current  program.  The 
addresses  are: the workspace mentioned above, the start of the Mallard  BASIC 
program, the end of the Mallard BASIC program, the start of variable  storage, 
the start of the array storage and, lastly, the end of the array storage.

Much  of the subsequent area is devoted to details of the disc,  and  includes 
buffers (each of 260 bytes) into which files can be read by the OPEN command.

14.2 CP/M Plus addresses

In  this  section,  the square brackets signify that a change  of  address  is 
needed if you are using J21CPM3.EMS, the version of CP/M Plus for the  Amstrad 

CP/M  Plus  uses the area 0-255 as a workspace (ROCHE> No: this is  the  "Page 
Zero" of memory.) and, normally, the area 62982-65535 as a system area (ROCHE> 
Isn't it the Resident BIOS? Where is the Resident BDOS?). The locations 64412-
64511  are known as the SCB ("System Control Block"), although only of few  of 
these  have  any  practical application to a  Mallard  BASIC  program  (ROCHE> 
Hahaha! "System" stands for "CP/M Plus Operating System", not Mallard BASIC!). 
We start with three work area jumps:

0-2.  This is a jump to the CP/M Plus warm boot, normally 64515 (gives the  A> 
prompt, if you prefer).

5-7.  A  jump to the current start of CP/M Plus (ROCHE> The  BDOS.),  normally 
62982. This address is used by all the BDOS calls. If locations 6 and 7 do NOT 
contain  6 and 246 respectively, there is a RSX ("Resident System  Extension") 
loaded,  and the Mallard BASIC area will be reduced. In this case,  be  clever 
with  extreme  caution!  (ROCHE> Simple: *NEVER* POKE  any  routine  in  "Page 
Zero".)  A point to be wary about is that, if you SUBMIT a  PROFILE.SUB  which 
ends with < run "prog", CP/M Plus will be extended downwards to cope with this 
(ROCHE>  The  SUBMIT  RSX  will be loaded in memory,  below  the  BDOS.),  and 
complications  can arise. It is better to end your PROFILE.SUB file  with  the 
line: BASIC PROG, and let PROG chain any further programs.

56-58.  This  is the jump to interrupts. It appears that CP/M Plus  does  not, 
itself,  use this jump much, but it is used more frequently in  Mallard  BASIC 
programs. Normally, the jump goes to 64929. (ROCHE> CP/M Plus for the  Amstrad 
PCW uses "Z-80 Mode 1 interrupts". See the Zilog doc for more info. The  Z/SID 
debugger for the Amstrad PCW uses RST 30/RST 6.)

(ROCHE> Some fields inside the "System Control Block" follow.)

64449. This normally contains 128. If this is changed to 64, all printing will 
go to the printer, instead of the screen. 192 will make printing go to  screen 
and printer.

64455.  Normally,  contains  64. Changing to 128 would  send  LPRINTs  to  the 
screen, but the main use of this is for alternative printers. POKE it with  16 
for  a Centronics printer and, if you have an Amstrad PCW9512, use 8  to  send 
the printing to a parallel printer.

64474.  Can be read to find the current default disc drive. 0 for A, 1 for  B, 
and 12 for M. POKE 64474,12 might be a alternative to OPTION FILES "M", but it 
could have dangers as the 'wrong' directory might be in the 'right' place,  if 
you see what I mean!

64487.  Error mode. Normally, 0. If POKEd with 254, CP/M Plus will produce  an 
error  message, but continue. In Mallard BASIC, the effect is that it  returns 
to  BASIC,  rather  than the A> prompt. POKEing with 255  disables  the  error 
message as well, a dubious procedure, except in very special circumstances.

64500-64504. Date (2 bytes) and Time (Hours, Minutes, and Seconds).

64515. Start of (ROCHE> Resident BIOS.) jump table. Some programmers take this 
address from 1 and 2, and calculate jumps or calls from it (ROCHE> This is the 
standard CP/M way of doing, since it is portable.) I dislike this, although it 
may be necessary for a program that is intended to work on other machines,  as 
well as the Amstrad PCW (ROCHE> There were at least a dozen of  microcomputers 
running  under  CP/M Plus. Personally, I was using Mallard BASIC 1.39  on  the 
Epson QX-10, when Amstrad PCW ruled.)

(ROCHE> Some fields inside the "Resident BIOS" follow.)

64602.  USERF. Well, you knew that, didn't you? (ROCHE> No, since you did  not 
give its name...)

64801  [64813].  Can be called to bring in a memory bank. Bank 0  consists  of 
blocks  128, 129, 131, and 135. Bank 1 is 132, 133, 134, and 135,  the  normal 
TPA  ("Transient  Program  Area"). Bank 2 is 128, 136, 131,  and  135.  Before 
calling, register A must be loaded with the bank number.

65002 [65014]. LPRINTs ASCII character in register C.

65007 [65019]. PRINTs ASCII character in register C.

65090 [65360]. Start of table of external devices.

65354 [65304] is start of XDPB for drive A, 65381 [65331] for drive B.

65413-65414 [65492-65943) will give the size of the M disc.

Chapter 15: Going places

In  the  previous chapter, we have had a close look at some of  the  locations 
that are accessible in the normal environment when Mallard BASIC is loaded. We 
have,  of  course,  made one foray beyond this limitation  when  we  discussed 
graphics. It is time, now, to increase the boundaries of our working area in a 
more general way. In this chapter, we shall consider how to PEEK and POKE  the 
unpeekable  and unpokeable! We shall look at USERF, the commonest  route  into 
the  forbidden  areas. We have already used one application of  USERF,  namely 
SCR_RUN, but there are many others.

This chapter will include two little but useful examples that do not quite fit 
anywhere else: a screen dump without using [PTR] and [EXTRA], and a routine to 
find the true position of the cursor on the screen -- POS often gives  strange 

15.1 Accessing the unaccessible

>From  this  point  on,  we shall be using blocks  that  are  not  normally  in 
accessible memory. This, however, is not really as big a problem as it  seems. 
Mallard BASIC has 31K free space, which is plenty to load a disassembler  such 
as the one in the Appendix, copy a complete 16K block, and still have room for 
most Mallard BASIC extensions.

I  find it convenient to copy the block to locations 40000-56383.  The  little 
program  below  hardly stretches the ingenuity of the code programmer  to  the 
limit, but it will serve us well.

        C15P1  (BLOCPROG)
        1 MEMORY 39977!
        2 POKE 39978!, 243, 126, 211, 240, 33, 0, 0, 1, 0, 64, 17, 64, 156
        3 POKE 39991!, 0, 237, 176, 62, 132, 211, 240, 251, 201
        4 INPUT "Block: "; a% : a% = a% AND 31 : a% = a% OR 128
        5 v = 39978! : CALL v (a%)

(ROCHE> In standard Z-80 code, this gives:


The  input  is entered with the block numbers we have used. For  instance,  to 
have  a  look  at the main BIOS block, 128 is input. The  rest  of  the  input 
ensures  that a number between 128 and 159 is entered. The 0 at the  start  of 
line  3  is useful, as we can make it into a toggle program, by  replacing  it 
with 235 (EX DE HL).

If we wish, we can run the program in the original form, do an odd POKE or two 
above  40000, change line 3 by replacing 0 with 235, run again, and the  block 
will subsequently contain the amendment.

15.2 CALLing to BIOS

In Chapter 12, we used SCR_RUN, which is a BIOS call (ROCHE> In this  section, 
each  time  Geoffrey  Childs  uses "BIOS", it means  "Banked  BIOS",  not  the 
"Resident  BIOS"...). While this is probably the most used of what are  termed 
the USERF calls, it is only one of many (ROCHE> Normally, BIOS functions  only 
do  one  thing, but Amstrad added a set of BIOS functions in the  Banked  BIOS 
(See the Locomotive doc.), which are accessed through USERF, instead of a jump 
table.).  The  BIOS in the different versions of CP/M Plus available  for  the 
Amstrad  PCW  varies, and there are only a limited number of calls  which  are 
certain to work in ALL versions, even if they do work in the one that you  are 
using. The main BIOS calls reside in a jump table going from addresses 128  to 
CP/M  PLUS FOR THE AMSTRAD PCW. This does no stop you CALLing to other  points 
in BIOS (block 128). Any call to BIOS can be made in a similar way to  SCR_RUN 
--  CALL  64602,  followed by a two-byte address (ROCHE> The  address  of  the 
extended BIOS function called.).

15.3 Screen dump

This little idea uses a CALL to a non-standard (ROCHE> Banked BIOS.)  address. 
It  will work with CP/M Plus Version 1.4 (probably the commonest  version  for 
the Amstrad PCW), but may well prove disastrous with any other version, unless 
the  address is changed. Suppose that you have written a program  which  plots 
graphs  of functions. One of the options that you would like is a screen  dump 
when the graph is completed. The snag is that it will be necessary to prompt a 
press  of [PTR] and [EXTRA], and this will spoil the screen. Instead of  this, 
we intercept BIOS once we have erased the prompt line. (Presumably, you  would 
have disabled the cursor and the status line earlier in the program.)

        400 PRINT CHR$ (27) "Y=A"; "Press S for Screen dump.";
        409 REM Cursor to Row 31, Column 33
        410 z$ = UPPER$ (INPUT$ (1))
        420 IF z$ = "S" THEN PRINT CHR$ (27) "Y=A"; SPACE$ (24); : GOSUB 900
        430 REM Etc...
        900 h = HIMEM : MEMORY h - 6 : POKE h - 5, 205, 90, 252, 114, 20, 201
        910 v = h - 5 : CALL v : MEMORY h : RETURN

(ROCHE> In standard Z-80 code, this gives:


15.4 Where is the cursor?

Many  BASICs have a command or a function to give the cursor position  on  the 
screen. Mallard BASIC does not, but details of this must be contained at  some 
point. This time, we can approach the problem in two ways. Either we find  the 
relevant location in BIOS, or we use a standard call which returns the row and 
column  in HL. The first method uses SCR_RUN as we need some code of our  own, 
the second uses a call from the jump table which gives the coordinates in  HL. 
The first method uses a non-standard address in BIOS. This means that it  will 
ONLY  work  if  CP/M  Plus Version 1.4 is  operating.  The  second  method  is 
preferable,  since  it  uses  a call to the  standard  (ROCHE>  Banked.)  BIOS 
jumpblock,  and will thus work with any version of CP/M Plus for  the  Amstrad 

        600 h = HIMEM : j = INT (h / 256) : k = 256 * j : MEMORY k - 1
        610 POKE k, 1, 12, j, 205, 90, 252, 233, 0, 121, 18, 112, 201
        620 POKE k + 12, 237, 75, 38, 40, 201
        630 CALL k (r%, c%) : PRINT "Row" r% "Column" c% : MEMORY h : RETURN

(ROCHE> In standard Z-80 code, this gives:


        600 h = HIMEM : j = INT (h / 256) : k = 256 * j : MEMORY k - 1
        610 POKE k, 229, 213, 205, 90, 252, 191, 0
        620 POKE k + 7, 68, 125, 209, 225, 18, 112, 201
        630 CALL k (r%, c%) : PRINT "Row" r% "Column" c% : MEMORY h : RETURN

(ROCHE> In standard Z-80 code, this gives:


Chapter 16: The keyboard

Computers are not like wives (or husbands). They do what you tell them. If you 
press  the  key next to [STOP], the Amstrad PCW will dutifully  produce  a  1. 
However, you could program it so that, if you pressed that key, you would get:

        What a load of rubbish!

printed  on the screen! Of course, you can do this by using  SETKEYS.COM,  but 
have  you ever felt that you would like to change a key temporarily  during  a 
Mallard BASIC program?

Mallard  BASIC  does  not have the facility to set a function  key.  This  is, 
perhaps, an omission on Locomotive's part (ROCHE> No: historically,  functions 
keys simply did not exist when BASIC (and CP/M) was created.), but it does not 
matter!  In this chapter, we look at ways to alter the operation of  the  keys 
during a program or possibly for a programming session.

16.1 Keyboard information

The last sixteen bytes of block 131 contain the most recent information  about 
the  state  of  the keyboard: namely, which keys were depressed  at  the  last 
keyboard  scan. A scan of the keyboard is done 50 times a second (ROCHE>  This 
happens  to  be  the frequency of 220 Volt current, the  electricity  used  in 
Europe.); in other words, once in every six interrupts. This may not appear to 
have great uses for the Mallard BASIC or CP/M Plus programmer, since there are 
easier methods to obtain details of a simple keypress.

However, the knowledge of this source of information may be useful if you wish 
for a combination of keypresses to mean something special. A familiar  example 
of this is [SHIFT][EXTRA][EXIT], a useful facility, but not one that you  want 
to activate accidentally!

The interrupt scan is not an ASCII scan, but uses the numbers of keys 00-80 as 
given  in  the  manual (book 1). The first 11 bytes have bits  set  or  reset, 
according  to  whether that key is being pressed. For example, [SHIFT]  and  Q 
(keys 21 and 67) would set the 11 bytes: 0 0 32 0 0 0 0 0 8 0 0 0. The bit set 
is  the key number MOD 8. Key number 72 sets bit 7 of the 10th byte, and  none 
of the other bits are relevant. The 11th byte contains information about  keys 
73-80. The last five bytes in this block of 16 refer to specific combinations, 
and include what appears to be a timer.

Block  128  contains  two  important  sets  of  tables.  Unlike  the  previous 
information,  the addresses of the tables depend on the version. The 81  bytes 
6016-6096  (in  CP/M Plus Version 1.4 for the Amstrad PCW) contain  the  ASCII 
characters  assigned  to the 81 keys in their normal state  (without  [SHIFT], 
[EXTRA],  or [ALT]). There are four more tables of 81 bytes that  follow  this 
one. They represent [SHIFT], [ALT], [SHIFT][ALT], and [EXTRA].

The table at 10358 in CP/M Plus Version 1.4 is the expansion key table;  There 
are 32 possible expansion keys, given quasi-ASCII codes of 128-159. The  table 
contains  the length of the expansion string, followed by the ASCII  codes  of 
the string for each of the 32 possible keys. It is conventional not to set 159 
to an expansion string, so that it can be used for a dead key.

16.2 Setting a key

Both  these  tables are more easily accessed through calls to  the  BIOS  jump 
table.  The  code  below  might be part of  some  instructional  program.  The 
programmer wants the user to try out what has been learnt, and then choose  to 
return  to the same or the next section of the program. If the user is not  an 
experienced  computer user, it is preferable to do this by a single  keypress. 
This  is  achieved by first setting two unused expansion keys to  restart  the 
program, and then activating [f1] and [f7] to use these strings.

        1500 h = HIMEM : j = INT ((h + 1) / 256) - 1 : k = 256 * j : MEMORY  k 
- 1
        1510 POKE k, 6, 154, 14, 10, 33, 128, j, 205, 90, 252, 212, 0, 201
        1520 POKE k + 128, 71, 79, 84, 79, 32, 49, 48, 48, 48, 13
        1530 m = k + 64 : POKE m, 6, 154, 14, 2, 22, 3, 205, 90, 252, 215,  0, 
        1540 CALL k : CALL m
        1550  POKE k + 1, 155 : POKE k + 133, 50 : POKE m + 1, 155 : POKE m  + 
3, 77
        1560 CALL k : CALL m
        1570 PRINT "To return to program:: Press [f1] for previous section."
        1580 PRINT "Press [f7] for nect section."
        1590 END

(ROCHE> This gives:


The code in 1510 has register B set to the expansion string number, register C 
to the length of string, and register pair HL to the address of the string  in 
memory.  1520 has the string GOTO 1000 [RETURN]. 1530 sets a  particular  key. 
Register  B  has  the ASCII code or the expansion string  number.  Register  C 
contains  the  key  number. Register D is set to which states  apply.  (1  for 
normal,  2 for shift, 4 for alt, 8 for shift-alt, and 16 for extra.)  In  this 
case, setting register D to 3 will activate both [f1] and [f2]. The variations 
set in 1550 will set the second key in a similar way, so that [f7] produces:

        GOTO 2000 [RETURN]

16.3 Grandpa

Grandpa  is  a dear old boy with all his wits about  him.  Unfortunately,  his 
fingers are not as nimble as they were, and when he tries to type "Granny", it 
tends  to go on the screen as "GGgrraaaannyyy". As you know, if you depress  a 
key,  and hold it down after the letter is typed, there is a  relatively  long 
pause  before the letter repeats, and subsequently short pauses  only  between 
further  repeats.  The first pause is called the "start up delay", and  is  30 
keyboard scans (or just over half a second). The subsequent pauses are "repeat 
delays", and are timed at 2 keyboard scans. For Grandpa, we would like to time 
them at 80 and 10 scans, respectively.

        10 h = HIMEM : MEMORY h - 10 : v = h - 9
        20 POKE v, 38, 80, 46, 10, 205, 90, 252, 224, 0, 201 : CALL v

(ROCHE> This gives:


Register H is set to the start up delay, and register L to the repeat delay.

There are other (ROCHE> Banked.) BIOS calls connected with the keyboard, but I 
think  that  those  are  the  three that you  may  need  with  Mallard  BASIC. 
Summarising them:

        Job                BIOS address        Registers set
        ---                ------------        -------------
        Expansion key      212 0               B, C, HL
        Set ASCII key      215 0               B, C, D
        Set keyspeed       224 0               H, L

Chapter 17: The 'M' disc

On  the  Amstrad  PCW8256, there is 112K of memory that is  not  used  by  the 
system,  on  the Amstrad PCW8512 and PCW9512, there is 368K. This  is  usually 
known as the 'M' disc, as CP/M Plus and BIOS are designed so that this  memory 
can be used in virtually the same way as a physical disc. Since the M disc  is 
memory,  access  to it is normally quicker (and quieter) than  to  a  physical 
disc. Hence, a good Mallard BASIC programmer will often transfer files to  the 
M disc which need to be read or written during a program.

This means that many programmers simply think of this memory as another  disc. 
A  better approach is to think of it as memory which is USUALLY used for  disc 
storage. It can be used for any other storage you like, with the proviso  that 
the storage will be lost every time the computer is switched off.

If  you wish to use part of this memory as a disc, and part of it for  someone 
else,  it is advisable to use the blocks near the end of this memory for  your 
own nefarious purposes!

Calling in an M-disc block cannot really be done from Mallard BASIC alone. You 
could  enter  OUT 242,137 to put the first block of it into  32768-49151  but, 
before you could use it, an interrupt would occur, and you would be back where 
you started. It is necessary to disable interrupts, and this can only be  done 
in code. It is a convenient feature that the last block of the Amstrad PCW8512 
memory  is  numbered 159. If you use this number on the Amstrad  PCW8256,  the 
block  does  not  exist,  but the computer  (ROCHE>  Well,  the  gate  array.) 
subtracts  16  and  works as though you have called for  block  143.  BUT:  be 
careful  with  blocks 144 to 152. You could write successfully to this  on  an 
Amstrad  PCW8512,  but the PCW8256 would overwrite a vital part of  memory  -- 
often with disastrous consequences!

17.1 Screen saving

I  said  that  you could save ANYTHING to the M disc,  but  you  are  probably 
wondering  exactly  what!  I  could be  infuriating  and  repeat...  anything! 
Instead, we will take one example. We will save and recall the screen. This is 
quite  a  powerful technique. Screens can be saved as random  (or  sequential) 
files  to disc, and this includes the M disc. Saving and recalling is  quicker 
on the M disc, but it is snail's pace, compared to a direct screen save.  When 
you  run this program, you will hardly realise that the screen is  turned  off 
and on during the recall routine, as it is so quick!

        10 h = HIMEM : j = INT ((h + 1) / 256) - 1 : k = 256 * j : MEMORY k  - 
        20 POKE k, 243, 62, 129, 211, 241, 60, 211, 242, 62, 159
        30 POKE k + 10, 211, 240, 1, 0, 56, 17, 0, 0, 33, 0, 128, 237, 176
        40 POKE k + 23, 61, 211, 240, 1, 208, 38, 17, 48, 21, 33, 48, 89
        50 POKE k + 35, 237, 176, 62, 132, 211, 240, 60, 211, 241
        60 POKE k + 44, 60, 211, 242, 251, 201
        70 l = k + 64
        80 POKE l, 243, 62, 8, 211, 248, 62, 129, 211, 241, 60, 211, 242,  62, 
        90 POKE l + 14, 211, 240, 1, 0, 56, 33, 0, 0, 17, 0, 128, 237, 176
        100 POKE l + 27, 61, 211, 240, 1, 208, 38, 33, 48, 21, 17, 48, 89
        110 POKE l + 39, 237, 176, 62, 132, 211, 240, 60, 211, 241
        120 POKE l + 48, 60, 211, 242, 62, 7, 211, 248, 251, 201

(ROCHE> In standard Z-80 code, this gives:


This code is put at the start of the program. A screen is then built up by any 
method you like, and CALL k is used when it is complete. The program continues 
and, at a later stage, you use CALL l. The first screen is recalled. You  MUST 
NOT  use CALL l before a CALL k, as roller RAM is also saved by  the  routine, 
and reading a fictitious roller table from the M disc would be disastrous.

As  you  can  see, the two routines are very similar. Some  33s  and  17s  are 
swapped  to  reverse the direction of block loads. In the second  routine,  we 
also  blank the screen during the operation, by the equivalent of  OUT  248,8, 
and recall it with OUT 248,7.

Chapter 18: Direct disc access

I start with an admission. I am wearing L plates for this part of the journey. 
An  admission,  not an apology, not a confession. All  programmers  should  be 
looking  for new fields to conquer. This book has shown how Mallard BASIC  can 
be adapted to operate in areas that used to be the sole province of CP/M Plus. 
Like  most  of  us, for a long time I took the attitude: "I know  how  to  use 
DISCKIT, there are CP/M Plus disc editors available: I will leave the discs to 

BIOS,  however,  does contain some disc access functions in the  main  (ROCHE> 
Banked.)  jump  table.  If they exist, they are there  to  be  explored.  This 
chapter  contains the results of two experiments. Firstly, there is  a  FORMAT 
program  that  runs from Mallard BASIC. Secondly, our very own  Mallard  BASIC 
disc editor. I have not, previously, seen any BASIC program that covers  these 

18.1 Format

Why  should you want a new FORMAT program? Two reasons. The first is that,  if 
you are a bit muddle-headed like me, there will be times when you want to back 
up a Mallard BASIC program, and you find you have no empty disc formatted.  Of 
course,  you can go back to CP/M Plus and use DISCKIT, but it would be  easier 
if you didn't have to leave Mallard BASIC.

The  second  reason  is that, believe it or not,  the  Mallard  BASIC  program 
appears to be quicker! I am not quite sure why, but I put forward a plus and a 
minus.  DISCKIT is a general purpose CP/M Plus program and, as such,  will  be 
checking  for  all sorts of versions and format types before it  hits  on  the 
right  thing to do. The second is that my program is probably much  cruder  in 
that  it assumes that, if it can work, the disc is in good physical  shape  -- 
oh, well, they usually are!

We  are going to attempt to write a program that will format an A disc on  the 
Amstrad  PCW8256 or PCW 8512. I have to apologise to Amstrad  PCW9512  owners. 
You may be able to modify it for your machine but, with L plates on, I  refuse 
to go into streets that I do not know well. As a bonus, it appears that we can 
get an extra 9K on the disc! I am working (unsuccessfully, so far) on the idea 
that we might get even more. As this is an experimental program, don't rely on 
discs formatted in the new way if the material is vital, until you have  given 
it  a thorough trial. It seems to work, but you can't be sure that it will  do 
so in every circumstance.

As  you know, if an A disc contains a J??CPM3.EMS program, it will  boot  CP/M 
Plus  from  switch on. This doesn't happen by magic! The first SECTOR  on  the 
first TRACK on the disc contains the code to do this. Our first problem is  to 
have  this  code  available, to put on the formatted disc.  While  it  can  be 
obtained from DISCKIT, it is simpler just to use an already formatted disc and 
write  it  into a random file that we can use later. This is the idea  of  the 
program C18P1.

        10 h = HIMEM : MEMORY 51199! : OPTION FILES "A"
        20 POKE 51200!, 1, 0, 0, 17, 0, 0, 33, 0, 201, 221, 33, 74, 255,  205, 
90, 252, 134, 0, 201
        30 v = 51200! : CALL v
        40 OPEN "r", 1, "formhead", 128 : FIELD 1, 128 AS a$ : u = 51456!
        50 FOR n = 0 TO 3 : b$ = "" : v = u + 128 * n : FOR m = v TO v + 127 : 
b$ = b$ + CHR$ (PEEK (m))
        60 NEXT : LSET a$ = b$ : PUT 1 : NEXT : CLOSE : MEMORY h

(ROCHE> Indented, this gives:

        10 h = HIMEM
        20 MEMORY 51199!
        30 OPTION FILES "A"
        40 POKE 51200!, 1, 0, 0, 17, 0, 0, 33, 0, 201, 221, 33, 74, 255,  205, 
90, 252, 134, 0, 201
        50 v = 51200! : CALL v
        60 OPEN "r", 1, "formhead", 128
        70 FIELD 1, 128 AS a$
        80 u = 51456!
        90 FOR n = 0 TO 3
        100     b$ = ""
        110     v = u + 128 * n
        120     FOR m = v TO v + 127
        130         b$ = b$ + CHR$ (PEEK (m))
        140     NEXT
        150     LSET a$ = b$
        160     PUT 1
        170 NEXT
        180 CLOSE
        190 MEMORY h

In standard Z-80 code, this gives:


The coding in line 20, while fairly simple, contains some definitions that  we 
shall  need to use in this chapter. The call is made to the  (ROCHE>  Banked.) 
BIOS function DD_READ, which needs the following parameters: Register B is set 
to the BANK, which is normally 0 for a (ROCHE> Banked.) BIOS call. Register  C 
is  set to the UNIT, which effectively means the drive: O for drive A,  and  1 
for drive B. Register D is set to the TRACK (0), and register E to the LOGICAL 
SECTOR  (0). HL is an address in common memory to which the 512 bytes  in  the 
sector will be written. Register pair IX is the start of what is known as  the 
XDPB  ("Extended  Disc Parameter Block"). The XDPB is a section  of  27  bytes 
which  gives various details about the disc drive. The XDPBs for drives A,  B, 
and M start at 65354, 65381, and 65408, respectively. (On the Amstrd  PCW9512, 
the locations are 65304, 65331, and 65487.)

Once the disc has been read into memory, the rest of the coding simply puts it 
into  strings, and saves it as a normal random file, which we  call  FORMHEAD. 
Once the program has run once, and the file is saved, there is no further  use 
for it.

Let  us  go into the idea of tracks and sectors more carefully, as this  is  a 
source  of  confusion.  So much so that it took me a day  to  debug  the  next 
program,  although it only meant changing one number! A disc is  divided  into 
TRACKS, which are further subdivided into SECTORS. In the case of the A  disc, 
there  are usually 40 tracks, each containing 9 sectors. Each sector  contains 
512 bytes (half a kilobyte). Tracks and sectors can be PHYSICAL or LOGICAL. In 
the case of tracks, it makes no difference, the A disc tracks are counted from 
0-39.  Sectors are a different matter. Logical sectors are counted  from  0-8, 
while  physical  sectors  are counted from 1-9. Just to  make  matters  worse, 
(ROCHE>  The  Banked.)  BIOS sometimes uses  logical  sectors,  and  sometimes 
physical... It is not very logical, but it made me physically angry!

This is the main program, which we shall call FORMAT. It should be on the same 
disc as the file FORMHEAD, which you have just created.

        10 DIM a$ (3) : OPEN "r", 1, "formhead", 128 : FIELD 1, 128 AS a$
        20 FOR n = 0 TO 3 : GET 1 : a$ (n) = a$ : NEXT
        30 CLOSE : PRINT "Press A when disc to format is in drive A."
        40 p = PEEK (64474!) : z$ = UPPER$ (INPUT$ (1)) : z = ASC (z$) : IF  z 
<> 65 THEN 30
        50  OPTION  FILES "a" : e = 74 : w = 65372! : t = PEEK (w) - 1 :  s  = 
PEEK (w + 1) - 1
        60 h = HIMEM : j = 192 : k = j * 256 : MEMORY k - 1
        70 FOR n = 1 TO s + 1 : POKE k + 72 + n * 4, 0, 0, n, 2 : NEXT
        80 GOSUB 260
        90  POKE k, 243, 62, 128, 211, 240, 58, 1, 0, 50, 255, 192,  254,  58, 
32, 5
        100  POKE  k + 15, 62, 42, 50, 22, 13, 62, 132, 211, 240, 251,  201  : 
        110 cv = (PEEK (49407!) = 58)
        120 IF cv THEN POKE 65372!, 42 : POKE 65359!, 183 : t = 41
        130 POKE k, 221, 33, 74, 255, 62, 0, 205, 90, 252, 149, 0, 201 :  CALL 
        140 POKE k, 86, 30, 229, 1, 0, 1, 33, 76, j, 221, 33, e, 255, 205, 90, 
252, 143, 0, 201
        150  POKE  k + 20, 1, 0, 1, 17, 0, 0, 33, 0, j + 1, 221, 33,  e,  255, 
205, 90, 252, 137, 0, 201
        160  POKE k + 39, 213, 94, 35, 86, 225, 35, 78, 35, 102, 105, 1,  128, 
0, 237, 176, 201
        170 PRINT CHR$ (27) "E" CHR$ (27) "H" CHR$ (27) "f" : FOR a% = 0 TO  t 
: FOR n = 0 TO s
        180 POKE k + 76 + n * 4, a% : NEXT : CALL k (a%) : PRINT CHR$ (27)  "Y 
"; a% : NEXT
        190 v1 = k + 20 : v2 = k + 39 : b% = k - 65280! : FOR n = 0 TO 3 :  c$ 
= a$ (n) : CALL v2 (b%, c$)
        200 b% = b% + 128 : NEXT : POKE k + 258, 40 - 2 * cv : CALL v1
        210 PRINT "Format completed." : MEMORY h
        220 OPTION FILES CHR$ (65 + p) : PRINT "Drive is "; CHR$ (65 + p) CHR$ 
(27) "e" : END
        230  PRINT f$ "on disc. Already formatted. Press C to carry on,  S  to 
        240 x$ = UPPER (INPUT$ (1))
        250 IF x$ = "C" THEN RETURN : ELSE IF x$ = "S" THEN END : ELSE 240
        260 POKE k, 1, 0, 1, 17, 2, 2, 33, 0, 193, 221
        270 POKE k + 10, 33, 74, 255, 205, 90, 252, 134, 0, 210, 173, 14, 201
        280 ON ERROR GOTO 310 : CALL k : ON ERROR GOTO 0 : f$ = FIND$ ("*.*")
        290 IF f$ <> "" THEN GOSUB 230
        300 RETURN
        310 RESUME 300

(ROCHE> This gives:


We  start the program with the disc containing FORMHEAD in the drive.  On  the 
Amstrad PCW8512, this could be drive B, if you like. The program pauses  until 
you  signal that the disc to be formatted is in drive A. The next  process  is 
the  subroutine at 260, which is omitted in DISCKIT (possibly to  some  users' 
deep regreat), is to check whether the disc is already formatted. It does this 
by attempting to read a sector and, if there is an error, everything is OK! If 
there  is  no error, there is trouble, and the program prompts the  user  with 
instructions to carry on or stop.

Lines 90-120 access (ROCHE> Banked.) BIOS block 128. If it finds it is reading 
CP/M Plus Version 1.4, it makes a change to allow 42 tracks, instead of 42.  A 
location  in  common memory is POKEd with 58 if, and only if, the  version  is 
1.4.  This is tested on return to Mallard BASIC, and other amendments  to  the 
XDPB are made if we can have 42 tracks.

Line  130 is probably usually unnecessary. It just tells the machine  that  we 
want format type 0, the usual A format. Line 140 is the (ROCHE> Banked).  BIOS 
format routine. Register pair BC is bank and unit, again. Register D  contains 
the  track number. Register E the filler byte, 229. (Goodness knows  why  this 
number was chosen (ROCHE> As far as I know, it was IBM who chose, circa  1972, 
this  value  as  an "empty" flag. Legend has it that  this  value  was  chosen 
because, in binary, it has an easy (?) value (11100101B) to note.) but, if you 
study  disc information, 229 is the signal for an erased file (ROCHE>  E5H.).) 
Register  pair  HL points to the 36-byte information section that we  fill  in 
line 180. Register pair IX points to the XDPB.

Line  150  is (ROCHE> Banked BIOS routine.) DD_WRITE. The parameters  are  the 
same as DD_READ (ROCHE> How interesting!). Line 160 is code to move the  array 
we made with FORMHEAD into memory, from which it can be read.

The information that we use in line 180 is track, head, sector, size, for each 
of  the nine sectors on the track. Don't ask me what head (ROCHE> Open a  dead 
hard disk, and you will understand.) means, 0 seems to work for it! The 2  for 
the size means 512 bytes.

And  that's  it.  We  have  made the  screen  look  just  like  DISCKIT  does. 

18.2 Disc editor

In  a sense, the program that follows illustrates better than words  what  the 
second  part  of the book is all about. Mallard BASIC is used as  the  working 
medium, with just two exceptions. The first is to provide the code to read and 
write to the disc, using (ROCHE> Banked BIOS routines.) DD_READ and  DD_WRITE, 
routines  that  we  have  already met. The second is  to  achieve  the  screen 
information  from the bytes read, a process that would be perfectly  practical 
in Mallard BASIC, but very much faster in code. The other feature is that this 
program would be impossible to write for most computers! The Amstrad PCW's  90 
by 32 screen is used (and needed) to the full.

There is no urgent need to use memory sparingly, so we have not bothered  with 
a relocatable program. The block 51200-51455 is used for the (ROCHE>  Banked.) 
BIOS  routines. 51456-51711 has the screen write routines. 51712-52223 is  the 
512-byte  block to which a sector is read. Above this, we leave space for  two 
strings to be created, into which the screen information will be written.

        10 GOSUB 1000
        20 d$ = c$ + "f" : e$ = c$ + "e" : PRINT
        21 PRINT d$; " Which drive? (A/B). Or press H for help."
        25 GOSUB 3000 : IF z = 72 THEN GOSUB 2500 : PRINT : GOTO 20
        30 uu = ASC (z$) - 65 : IF uu < 0 OR uu > 1 THEN 25
        40  PRINT e$ : INPUT " Enter track number and press [RETURN]: ", t$  : 
t% = VAL (t$)
        50  INPUT " Enter sector number and press [RETURN]: ", s$ : s%  =  VAL 
        55  PRINT d$; : tm = PEEK (65372! + uu * 27) * (uu + 1) : sm  =  (PEEK 
(65373! + uu * 27) - 1)
        60 k = 51200! : POKE k, 1, uu, 1, 17, s%, t%, 33, 0, 202, 221, 33,  74 
+ uu * 27, 255
        61 POKE k + 13, 205, 90, 252, 134, 0, 210, 173, 14, 201
        70 g = k + 30 : POKE g, 1, uu, 1, 17, s%, t%, 33, 0, 202, 221, 33,  74 
+ uu * 27, 255
        71 POKE g + 13, 205, 90, 252, 137, 0, 210, 173, 14, 201
        80 ON ERROR GOTO 3010 : CALL k : ON ERROR GOTO 0
        90 PRINT c$ "E" c$ "H" : GOSUB 1500
        100 GOSUB 2000
        110 i$ = "TKAUHQ+-" + CHR$ (22) + CHR$ (28) + CHR$ (4)
        120 GOSUB 3000 : i = INSTR (i$, z$) : IF i = 0 THEN 120
        125 IF (i = 1 OR i > 6) AND uf THEN GOSUB 3040 : GOTO 120
        126 IF i = 2 OR i = 3 THEN uf = 1
        130 ON i GOSUB 150, 200, 250, 300, 350, 400, 450, 500, 550 : GOTO 120
        140 ON ERROR GOTO 0 : GOTO 120
        150 GOSUB 1604 : GOSUB 1610
        152 PRINT FN a$ (3, 76) e$; : INPUT t$ : t% = VAL (t$) : PRINT
        154  PRINT FN a$ (6, 76); : INPUT s$ : PRINT : s% = VAL (s$)  :  PRINT 
        156 GOSUB 800 : RETURN
        200  GOSUB  700 : GOSUB 720 : GOSUB 1602 : z$ = INPUT$ (1) : q  =  ASC 
        205 GOSUB 1600 : GOSUB 900 : GOSUB 1602 : RETURN
        250 GOSUB 700 : GOSUB 710 : GOSUB 720 : GOSUB 1600 : GOSUB 900 : GOSUB 
1602 : RETURN
        300 GOSUB 1600 : uf = 0 : CALL g : GOSUB 1602 : RETURN
        350  GOSUB  2500  : PRINT : PRINT TAB (30) "Press a  key  to  run  the 
        351 GOSUB 3000 : RUN
        400 PRINT c$ "e" c$ "E" c$ "H" : MEMORY hm : END
        450 s% = s% + 1 : IF s% > sm THEN s% = 0 : t% = t% + 1
        452 GOTO 800
        500 s% = s% - 1 : IF s% < 0 THEN s% = sm : t% = t% - 1
        502 GOTO 800
        550 GOSUB 700 : GOSUB 720 : GOSUB 1602 : z = 0
        552 WHILE z <> 13 : q$ = "&H" : FOR n = 1 TO 2
        554 GOSUB 3000 : IF z = 13 THEN 560
        555 IF z < 48 OR (z > 57 AND z < 65) OR z > 70 THEN 554
        556 q$ = q$ + z$ : NEXT : GOSUB 1600 : q = VAL (q$) : GOSUB 900 : b  = 
b + 1
        558 GOSUB 1602 : WEND
        560 RETURN
        700 GOSUB 1604 : PRINT FN a$ (26, 74); "BYTE NUMBER";
        702 PRINT FN a$ (27, 76); : INPUT b$ : b = VAL ("&H" + b$) : PRINT
        704 IF b < 0 OR b > 511 THEN GOSUB 730 : GOTO 702
        706 RETURN
        710 PRINT FN a$ (28, 74); "VALUE";
        712 PRINT FN a$ (29, 76); : INPUT q$ : q = VAL (q$) : PRINT
        714 IF q < 0 OR q > 255 THEN GOSUB 730 : GOTO 712
        716 GOSUB 900 : RETURN
        720  FOR  n = 26 TO 29 : PRINT FN a$ (n, 74); SPACE$ (14);  :  NEXT  : 
        730 GOSUB 720 : PRINT FN a$ (26, 74), "RE-ENTER"; : RETURN
        800  ON ERROR GOTO 3020 : IF t% < 0 OR t% > tm THEN GOTO 3019  :  ELSE 
GOSUB 1610
        801 GOSUB 1620 : POKE 51204!, s%, t% : POKE 51234!, s%, t% : CALL k
        802 PRINT FN a$ (3, 76) t% FN a$ (6, 76) s% : GOSUB 2000 : RETURN
        900 hb = INT (b / 16) : lb = b MOD 16 : q$ = r$ + HEX$ (q, 2) + o$
        902 PRINT FN a$ (hb, 6 + lb * 3); q$;
        905 qq = q : IF q < 33 THEN qq = 46
        910  POKE  51712!  + b, q : PRINT FN a$ (hb, 56 + lb);  CHR$  (qq);  : 
        999 END
        1000 PRINT CHR$ (27) "0" CHR$ (27) "E" CHR$ (27) "H" : hm = HIMEM
        1001 MEMORY 51199! : v = 51456! : u = 51440!
        1005 POKE 52224!, 27, 89, 32, 88 : POKE 52244!, 255
        1010 POKE 52248!, 27, 89, 32, 38 : POKE 52300!, 255
        1011 POKE u, 17, 255, 0, 14, 110, 195, 5, 0
        1012 c$ = CHR$ (27) : r$ = c$ + "p" : o$ = c$ + "q"
        1013 pa$ = r$ + " BUSY " + o$ + " "
        1014 pb$ = r$ + " KEYPRESS " + o$
        1015 pc$ = r$ + " ENTER " + o$ + " "
        1016 DEF FN a$ (r, c) = c$ + "Y" + CHR$ (32 + r) + CHR$ (32 + c)
        1020  POKE  51456!, 221, 33, 4, 204, 17, 26, 204, 126, 198,  32,  221, 
119, 254, 18, 126
        1030 POKE 51471!, 183, 23, 23, 23, 38, 101, 111, 41, 19, 19, 6, 16
        1040 POKE 51483!, 126, 254, 33, 48, 2, 62, 46, 221, 119, 0, 221, 35
        1050 POKE 51495!, 126, 31, 31, 31, 31, 230, 15, 205, 128, 201
        1060 POKE 51505!, 126, 230, 15, 205, 128, 201, 62, 32, 18, 19, 35, 16, 
        1070  POKE 51518!, 17, 0, 204, 14, 9, 205, 5, 0, 17, 24, 204,  14,  9, 
195, 5, 0
        1080 POKE 51584!, 198, 48, 254, 58, 56, 2, 198, 7, 18, 19, 201
        1090 RETURN
        1500 FOR n = 0 TO 31 : h$ = HEX$ (n * 16, 3) : PRINT FN a$ (n, 0); h$; 
        1501 PRINT FN a$ (0, 0);
        1502 GOSUB 1600
        1504 PRINT FN a$ (2, 74) "TRACK";
        1505 PRINT FN a$ (3, 76) t%
        1506 PRINT FN a$ (5, 74) "SECTOR";
        1507 PRINT FN a$ (6, 76) s%
        1508 PRINT FN a$ (8, 74) "Press:";
        1510 PRINT FN a$ (10, 74) "T Tr/Sec";
        1512 PRINT FN a$ (12, 74) "K Amend by";
        1514 PRINT FN a$ (13, 74) " Keypress";
        1516 PRINT FN a$ (14, 74) "A Amend by";
        1518 PRINT FN a$ (15, 74) " ASCII";
        1524 PRINT FN a$ (16, 74) " U Update";
        1526 PRINT FN a$ (17, 74) " disc.";
        1528 PRINT FN a$ (18, 74) "H Help";
        1530 PRINT FN a$ (20, 74) "+ Next";
        1532 PRINT FN a$ (21, 74) " Sector";
        1534 PRINT FN a$ (22, 74) ". Previous";
        1536 PRINT FN a$ (23, 74) " Sector";
        1538 PRINT FN a$ (24, 74) "Q Quit";
        1540 RETURN
        1600 PRINT : PRINT FN a$ (0, 74); pa$; : RETURN
        1602 PRINT : PRINT FN a$ (0, 74); pb$; : RETURN
        1604 PRINT : PRINT FN a$ (0, 74); pc$; : RETURN
        1610 PRINT FN a$ (3, 76) SPACE$ (11);
        1611 PRINT FN a$ (6, 76) SPACE$ (11); : RETURN
        1620 PRINT FN a$ (3, 76) t%
        1621 PRINT FN a$ 6, 76) s% : RETURN
        2000 GOSUB 3050 : GOSUB 1600 : POKE u + 1, 255 : CALL u
        2001  FOR a% = 0 TO 31 : CALL v (a%) : NEXT : CALL u : LEB a, 0, 0   ' 
<---- Lightning BASIC command...
        2002 POKE u + 1, 36 : CALL u : GOSUB 1602 : RETURN
        2500 PRINT c$ "E" c$ "H"
        2502  PRINT "To run the program, select your disc by pressing A or  B, 
and choosing a sector and track."
        2504  PRINT  "0  and 0 will do, if you  are  just  experimenting!  The 
program produces a full sector listing"
        2506 PRINT "in hex. At the top right of the screen, you will see:"
        2508  PRINT  :  PRINT "BUSY. Don't do  anything  until  this  changes! 
Computer at work! Or:"
        2510  PRINT : PRINT "KEYPRESS. One of T K A U H Q + or -  is  awaited. 
        2512 PRINT : PRINT "ENTER. At the ? prompt, an input is awaited. Enter 
and press [RETURN]."
        2514 PRINT : PRINT "T: You have asked to enter a new track and sector. 
Enter each and press [RETURN]."
        2516 PRINT "K: You amend your chosen byte by entering the number of it 
in HEX, and the pressing the key for the alteration."
        2518 PRINT "A: The same, but you enter the value of the code you  wish 
to enter in decimal. If you want to use HEX, you may enter with &H. To  change 
X to A, enter 65 or &H41."
        2520 PRINT "U: When you have done amendments, they will be entered  on 
screen but NOT on the disc, as yet. Press U to update the DISC."
        2522 PRINT "Q: Quit, that's easy!"
        2524 PRINT "+: Go to the next sector."
        2526 PRINT "-: Go back a sector. (Either of the [+] and [-] keys  will 
        2528 PRINT : PRINT "ERROR MESSAGES: There is not room on the screen to 
give them conventionally:"
        2530  PRINT "Three beeps and blank screens: Sector or  track  illegal. 
        2532  PRINT "Flash: You have amended without updating. Press U if  you 
meant to update. Repeat the keypress if it was intentional not to update."
        2534 RETURN
        3000 z$ = UPPER$ (INPUT$ (1)) : z = ASC (z$) : RETURN
        3010 PRINT "Re-enter track & sector." : RESUME 40
        3019 SCRUNCH  ' <---- Voluntary error (see text)
        3020 FOR n = 1 TO 3 : PRINT CHR$ (7); : OUT 248, 8 : GOSUB 3060
        3021 OUT 248, 7 : GOSUB 3060 : NEXT
        3030 GOSUB 1610 : GOSUB 1604 : RESUME 150
        3040 uf = 0 : FOR n = 1 TO 5 : PRINT CHR$ (7) : NEXT : OUT 247, 128  : 
        3050 IF s% > sm OR t% > tm THEN 3019 : ELSE RETURN
        3060 FOR nn = 1 TO 500 : NEXT : RETURN

(ROCHE> This gives:


The  subroutine  at 1000 initializes various things, and installs  the  screen 
writing  code. This is called and, up to about line 100, the coding  is  taken 
from an earlier development of the program. The line numbers for the sector on 
the left, and the menu on the right, are then installed, using the routine  at 
1500. They remain in place throughout the program. The middle of the screen is 
then filled from the sector of your initial choice.

The  menu operates mainly by keypress, and you will see the  various  options. 
These  are  described in greater detail by the Help page at 2500. If  you  are 
typing  in this program, it is sensible to ignore these lines. 2500 RETURN  is 
all you need.

The  menu is fairly self-explanatory, with the usual INSTR and ON-GOTO  lines. 
You will see that each option is very short. This is because we have made much 
use  of subroutines. There was not room to include the Direct printing  option 
on the menu (use [ALT] and [D]). Direct printing asks for a prompt number  for 
the  start. Subsequently, bytes can be typed in as hex numbers. You  must  use 
'00'  for 0, and '0F' for decimal 15. A [RETURN] signals that  enough  numbers 
have  been changed. You should then press 'U' for Update if your amendment  is 

The  program has been slightly modified on the disc, so that it will  work  on 
the Amstrad PCW9512, as well as the Amstrad PCW8256/8512.

The subroutines from 700-900 are the workhorses for most of the options on the 
menu.  Those at 1600 are used for the minor printing changes in the menu.  The 
routine at 2000 does the screen print out for a sector.

At 3000, we have a keypress routine, followed by various error routines.  With 
less  space than usual for the menu, the error routines take on an  additional 
importance.  While  it  would  be possible to  update  the  disc  after  every 
amendment,  it  is quicker and less error prone to ask for an  update  at  the 
user's discretion. As this can be forgotten, a flag (named uf) keeps track  of 
any amendments, and warns if an update is not done before going on to the next 
sector. The other error is an illegal track or sector. While the program finds 
this  out eventually, it can cause horrible grinding noises. It is  better  to 
send  it  to an error routine while it is prefectly legal. GOTO 3019  and  the 
line  3019 itself ("SCRUNCH") may be a novel way of producing an  error!  Both 
errors produce variations on the "son et lumière" theme.

For  the less dedicated hackers, there might be a question of what use a  disc 
editor  can be. An obvious and simple use is an erased file. Track 1 Sector  0 
is the start of the directory. Suppose you have accidentally erased MYPROG.BAS 
from the A disc. When you edit this sector (or perhaps the next), you will see 
a line containing MYPROG.BAS. It will start with an E5 code, the erase signal. 
Replace  with  00,  by using the ASCII option. Update and  quit  the  program. 
MYPROG  will  be back in the directory when you DIR. (ROCHE>  Geoffrey  Childs 
does not seem to use "user numbers" because, if you patch '00', the file  will 
appear in the directory of user 0. But you could just as well change this user 
number to any one legal under CP/M Plus. See the "CP/M Plus User's Guide".)

Chapter 19: Interrupts

If  you  tell  most  of your friends that  have  written  an  interrupt-driven 
program,  they  will probably tell you that their missus is  like  that,  too! 
However, you may deeply impress the more knowledgeable -- and rightly, too!

Using  this  technique is not easy. If you try to write such  programs,  there 
will  be a lot of trial and error, despair and crashes. At least,  there  will 
be, unless you are a very much better programmer than I am. The only interrupt 
program that I have ever written that worked first time was for the sharp  MZ-
80K.  It  consisted of two bytes -- 118, 201 (HALT, RET). It is  probably  the 
shortest  machine  code  program  on record, and just  about  the  longest  in 
execution! The clock was designed, on that machine, so that the only interrupt 
that would restart after HALT was after the clock reached a time of  11.59.59! 
I  think  that it worked O.K -- for obvious reasons, I did not  test  it  from 
switch on!

The idea of interrupt programming is to create our own diversions, so that the 
Amstrad  PCW  does what we want it to do, as well as what it wants to  do  for 
itself.  When it has done our job and its own interrupts, it carries  on  with 
the normal program.

19.1 Excuse me, I'll only be a microsecond!

The  Amstrad PCW is interrupted 300 times in every second. An interrupt  is  a 
routine  that is carried out as a diversion from the program  being  executed. 
For  example,  it might be a keyboard scan. If you press the [STOP]  key,  the 
program  stops. Your Mallard BASIC program will not contain lines telling  the 
computer to check for [STOP]. An interrupt will have noted the keypress, and a 
Mallard  BASIC  routine  will check whether this has  happened  as  every  new 
Mallard BASIC command occurs.

In a Mallard BASIC program, a jump to location 56 will cause a further jump to 
the  interrupt  routine (at 64929). For certain ideas, it may be  possible  to 
relocate  the jump at 56 to go via our own destination. This can be  effective 
in  simple cases, but there is no guarantee of any regularity, as most of  the 
interrupts do not go through this route. (As we mentioned, the Sharp only used 
it  once in 12 hours, though there will always be many more occasions  on  the 
Amstrad PCW.)

There are three main difficulties on the Amstrad PCW:

     1.  The only reliable way to intercept the interrupts is to use  code  in 
block  128,  to trap them before they reach the relevant  routine  in  (ROCHE> 
Banked.) BIOS.

     2. An interrupt to an interrupt must be quick. It is all too easy to give 
it so much to do that, by the time it returns, it is catching up with its  own 
tail -- put it in cruder terms, if you wish!

     3. An interrupt to an interrupt must disable other interrupts. This means 
that  it  is impossible to use routines, such as many BDOS calls,  which  will 
disable interrupts, but then enable them again.

Don't  think that I am trying to discourage you from trying! It is just  that, 
when  your  first attempt goes wrong, it might be due to one  of  the  reasons 

Locations  65191 and 65192 (65143-65144 on the Amstrad PCW9512) are  of  great 
importance. They contain the address in Block 128 where the interrupt routines 
start. The contents differ, depending on the version of CP/M Plus used.  Block 
128  has a gap of 32 bytes at location 64. If you use these bytes  to  contain 
your own code, followed by a jump to the original contents of 65191-65192, and 
in effect POKE 65191,64,0, all interrupts will go by your route.

32  bytes  may not seem very much to play with. However, it is not as  bad  as 
that!  Block  135  (common memory) is still with us when we  go  into  (ROCHE> 
Banked.) BIOS, and the 32 bytes can contain a call to anywhere in this region. 
If you include such a call, it is advisable to make it conditional. It is very 
unlikely that you will need your code carried out 300 times a second!

That  sounds  tough going -- it is! When you actually see  a  program  listing 
using this technique, it will LOOK a little easier.

19.2 The routine timer

        10 x9 = PEEK (65191!) : y9 = PEEK (65192!): z9 = 167
        15  IF PEEK (64644) <> 3 THEN x9 = PEEK (65143) : y9 = PEEK (65144)  : 
z9 = 119
        20 h9 = HIMEM : k9 = INT ((h9 + 1) / 256 - 1) : j9 = k9 * 256 : MEMORY 
j9 - 1
        30 POKE j9, 1, 9, k9, 205, 90, 252, 233, 0, 201
        40 POKE j9 + 9, 33, 21, k9, 1, 12, 0, 17, 64, 0, 237, 176, 201
        50 POKE j9 + 21, 229, 42, 33, k9, 35, 34, 33, k9, 225, 195, x9, y9
        60 POKE j9 + 35, 33, 64, 0, 243, 34, z9, 254, 251, 201
        70 CALL j9 : m9 = j9 + 35 : CALL m9 : POKE j9 + 36, x9, y9
        80 POKE 64502!, 0, 0, 0 : POKE j9 + 33, 0, 0
        100 REM Put the routine here.
        110 FOR n = 1 TO 10000! : NEXT
        60000 CALL m9 : DEF FN a (x) = 10 * (INT (PEEK (x) / 16)) + (PEEK  (x) 
MOD 16)
        60010 t = FN a (64502!) * 3600 + FN a (64503!) * 60 + FN a (64504!)
        60020 i = PEEK (j9 + 33) + 256 * PEEK (j9 + 34)
        60030 IF ABS (t - i / 300) > 1 THEN i = i + 65536! : GOTO 60030
        60040 PRINT "Time was" ROUND (i / 300, 2) "seconds." : MEMORY h9

(ROCHE> This gives:


It is not too difficult to use the internal timer to keep track of time to the 
nearest  second.  For  greater accuracy than this, we will  need  to  use  the 
interrupt  system. This is about as simple a program as one could write  as  a 
useful example of interrupts. There are 300 interrupts per second and, as each 
of  them  is recorded by the program, we can measure the timing of  a  Mallard 
BASIC  routine to 1/300th of a second. We actually print just to  the  nearest 
hundredth of a second.

Line 10 to 15 finds the address of start of interrupts in block 128. This DOES 
vary  with different versions of CP/M Plus for the Amstrad PCW. The  diversion 
is  coded in line 50. This increments a counter at j9+33 and j9+34,  and  then 
reverts to the old interrupt address. Lines 30 and 40 install it at address 64 
in  block 128, using our old favourite SCR_RUN. Line 60 POKEs 65191-65192  (or 
65143-65144)  with  this new address. It is advisable  to  disable  interrupts 
while  this happens, so we use code for it. In line 70, we call both  routines 
and amend the one in line 60, so that we can restore the old interrupt address 
at the end. Line 80 zeroes both the normal clock and our new one.

Between  100 and 60000, you can merge a program of your own that you  wish  to 
time.  The idea of using both clocks is that the normal clock acts as a  crude 
timer  which  will show if the interrupts have gone 'round the  clock',  which 
they will do in about three minutes. If this has happened, t and i/300 will be 
sufficiently different to be noticed by the test in line 60030.

On this note, we end our tour. We hope that you have enjoyed it. If so,  don't 
forger to tip the driver!


A1: Program disc

The  disc provided with this book contains all the programs from the text,  so 
that it is possible to test them without the labour of typing in the text. The 
names  of  the programs are taken from the text. C18P3, for  example,  is  the 
third  program listed in Chapter 18. With the exception of C9P2  (which  needs 
Lightning BASIC, as explained in the text), all the programs in the first part 
of the book (Chapters 1-9) only need Mallard BASIC.

The  programs in the second part of the book may require a multiple POKE.  The 
easiest  way  to  deal with this is to RUN"DWBAS"  immediately  after  loading 
Mallard BASIC. An alternative is to use the coding in Appendix 4 to install  a 
multiple POKE, but make no other changes to Mallard BASIC.

The programs in the Appendices A5 to A8 do require DWBAS. They will also  work 
if you have used Lightning BASIC, instead of DWBAS.

It  is  appropriate  to  mention the question  of  copyright.  Ideas  are  not 
copyright, and the main intention of the book is to give you ideas! It is also 
true  that  those who buy this book are entitled to incorporate  the  programs 
into those that they will use for themselves. The question comes when a reader 
has  used one of our programs as part of a bigger program that is to be  sold. 
In  general,  we  would  encourage this on  the  assumption  that  a  suitable 
acknowledgement  was made. If in doubt, consult us -- we are friendly  people, 
really! (ROCHE> Too bad: I have been unable to find you!)

We  reserve the right to make amendments or enhancements to the program  disc, 
and your disc will probably contain a README.DOC file. Read it! It is possible 
that  a few disc programs may contain slight variations from the text  of  the 
book, for this reason.

A2: Turnkey discs

You  should always make a copy of a master -- so they say. Let us assume  that 
you  have done so, or will do so! Many people prefer to make a  turnkey  disc, 
one that will load from switching on the computer. You will find that there is 
a  PROFILE.SUB file on the program disc. If you don't like it, you can  easily 
edit it.

First,  use  DISCKIT  to copy our program disc onto a new  disc.  Put  in  the 
Asmstrad Master CP/M Plus disc, and type:

        PIP [RETURN]

At  PIP's '*' prompt, type in succession these three lines, and wait  for  the 
next '*' prompt:

        m:=j*.ems [RETURN]
        m:=basic.com [RETURN]
        m:=submit.com [RETURN]

Now, put into drive A a new formatted disc and, at the '*' prompt, type:

        a:=m:*.* [RETURN]

When the copying is complete, your turnkey disc is ready for use.

OK.  You  know a better method. Fair enough, but I thought I had  better  tell 
you, just in case.

A3: Documentation for DWBAS

There are so many differences between Mallard BASIC Version 1.29 and 1.39 that 
affect  DWBAS that it is better to have a new program, if you  have  installed 
1.39 (the Mallard BASIC for the Amstrad PCW9512). This is called DW139. So, if 
you have a Amstrad PCW9512, read DW139 for all our references to DWBAS. If you 
do type RUN"DWBAS" after you have loaded 1.39, it won't matter, as the correct 
program will be chained after a check. It will just take a little longer!

To  load DWBAS, load Mallard BASIC in the normal way from CP/M Plus, and  then 
put  in the disc with DWBAS. Type RUN"DWBAS", press [RETURN], and that is  all 
there is to it. If you have Mallard BASIC and DWBAS on the same disc, you  can 
simply type BASIC DWBAS from CP/M plus to get the same result.

DWBAS is not designed to contain any very complex new commands but, generally, 
to make life simpler for you. There is access to graphics by one command,  but 
you  would  need a much larger extension for all the bells and  whistles  that 
other extended BASICs could contain.

The extra command in DWBAS can all be obtained by entering LDW, followed by  a 
single  letter. For example, LDW c will clear the screen. However, as we  know 
that  typing THREE whole letters is hard work, all the DWBAS commands  can  be 
obtained  by # followed by the letter, with no space in between. Thus,  #c  is 
all that is actually needed for a screen clear. One or two DWBAS commands need 
some  numbers  (parameters) to follow, and these must be  separated  from  the 
ORIGINAL command and each other by commas (",").

Parameters  can  either be numbers or variables that evaluate to  the  numbers 
that  you want. If you wish to print the word HELLO on row 10, column  8,  you 
could use: #a,10,8:?"HELLO".

(It is much simpler than using CHR$ (27) + "Y" + CHR$ (42) + CHR$ (40),  isn't 
it?)  Let's look at the commands without any more waffle. If  x1,x2,x3...  are 
used, they stand for the parameters that you have to add after the command.

DWBAS commands

#a,x1,x2: Move the cursor so that printing starts at row x1, column x2.
#b: Beep.
#c: Clear the screen. (See note below.)
#d: Disable the cursor.
#e: re-Enable the cursor.
#f,x1:  Flash  the  screen  x1 times. (0-255, but  0  flashes  256  times.  No 
#g,x1,x2:  Print a high-resolution Graphics point. x1 takes value 0-359.  Left 
screen = 0. Right screen = 359. x2 takes values 0-255. Top = 0. Bottom = 255.
#h: Home. The cursor goes to top left. Any windows are also cancelled.
#i: Inverse video. Whole screen.
#j: Save the cursor position as in ?CHR$(27)"J".
#k: Restore the cursor position saved by above, as in ?CHR$(27)"K".
#i: This is not used!
#m: Message line enabled. (Makes screen 31 rows.)
#n: No message. (Makes screen 32 rows.)
#o: Off with reverse video and/or underlining.
#p: reset the Printer.
#q: near letter-Quality print.
#r: subsequent PRINT statements in Reverse video.
#s: Small print from printer (condensed).
#t,x1: Changes TAB to TAB with CHR$ (x1), instead of spaces. Example:

        #t,46 changes tabbing to full stops,
        #t,32 reverts to normal.

#u: Underline subsequent PRINT statements.
#v: cancel inverse Video.
#w,x1,x2,x3,x4: Window start at row x1, colum x2, x3 deep, and x4 wide.

Multiple POKEing

An  additional facility in DWBAS is to allow a multiple POKE. The  meaning  of 
this  is  that  a  row  of figures can be POKEd in  at  the  same  time.  POKE 
40000,11,12,13  is the same as POKE 40000,11 : POKE 40001,12 : POKE  40002,13. 
This  makes  code  writing  much easier and,  incidentally,  much  quicker  in 
operation than use of DATA statements.

You can also use LIST as a program line, which is useful in some demonstration 
programs.  If  you  have disabled the cursor, it will  be  re-enabled  when  a 
program  ends or is broken. Using #d in direct mode will, therefore,  normally 
only disable the cursor for an instant.

The screen clear is more complex than the usual one, in that it resets  roller 
RAM. This may be a technical point but, very simply, some graphics and  screen 
dumping  routines  work much quicker if one can rely on a roller  reset  every 
time the screen is cleared. The roller will remain reset, unless any scrolling 
is done.

DW139  is used if, and only if, you are using Mallard BASIC Version 1.39.  The 
operation is virtually the same, except that #p, #q, and #s, are disabled,  as 
they will not be much use with the Amstrad PCW9512 printer.

A4: Multiple POKE

If  you  don't want to use DWBAS, you can use one of these short  programs  to 
install  the  multiple POKE that we use in the second half of the  book.  (For 
instance,  you may want to write a program of your own that does  not  require 
any of the other facilities of DWBAS.) Having saved the program, it should  be 
RUN  immediately  after loading Mallard BASIC in the normal  way.  The  second 
program  should be used instead of the first if you use Mallard BASIC  Version 
1.39. If you install DWBAS, don't run these programs as well.

        10 DATA 209, 18, 62, 44, 190, 192, 35, 19, 213, 195, 69, 93
        20 DATA 33, 72, 93, 54, 195, 35, 54, 104, 35, 54, 1, 201
        30 FOR n = 360 TO 383 : READ m : POKE n, m : NEXT : v = 372 : CALL v

        10  DATA 18, 62, 44, 190, 192, 35, 19, 195, 236, 93, 33, 233, 93,  17, 
129, 1
        20 DATA 6, 9, 26, 119, 19, 35, 16, 250, 201, 205, 29, 71, 205, 67, 19, 
195, 104, 1
        30 FOR n = 360 TO 393 : READ m : POKE n, m : NEXT : v = 370 : CALL v

(ROCHE> This gives:


A5: Disassembler

A5  is a disassembler, written in Mallard BASIC. RUN"A5" from  Mallard  BASIC. 
That's  really about all you have to know about running it! Enter the  decimal 
(or  hex number) at the prompt, and you will see the code. (ROCHE> That  means 
that  A5 is not a real disassembler, but just a routine listing the  code,  as 
found inside each debugger.) If you enter hex, use the usual conventions.  For 
example,  if  you want to start at 49152 (decimal) which is C000  hex,  either 
enter  49152 or &HC000, which is what Mallard BASIC accepts. When you  have  a 
page of code, you get various self-evident prompts.

BUT,  at  this point, there are one or two prompts that the  progrm  does  not 
give.  If  you  like,  we  can call it a hidden  menu.  Three  of  these  five 
facilities  are TOGGLES (meaning: if it is on, it goes off, and it it is  off, 
it goes on!). All five are called by the [ALT] key and a letter pressed at the 
same time. If you press [ALT] and the relevant letter, the computer beeps  for 
the toggles, and you carry on from the original prompt.

[ALT]-V  changes  from normal video to inverse, or vive versa.  [ALT]-Z  gives 
zero-suppress  (A series of 0s in the code is not disassembled, and  signalled 
by GAP.) (Toggle.). [ALT]-P echoes the code to the printer (Toggle.).

[ALT]-D  allows  disassembly  of  a program placed in  memory  as  if  it  was 
somewhere  else in memory. This is not a beginner's tool, but an  example  may 
suffice  to  give some idea of why it is there. If you would like  to  examine 
SETKEYS.COM  (on  the  Master Disc 2), you could put it in  memory  at  55000. 
(ROCHE>  So,  A5  can  only list the mnemonics of a code  in  memory,  like  a 
debugger.  It cannot disassemble a file, like a disassembler. QED!) You  could 
then use [ALT]-D, giving 55000 and 256 to the two prompts, and the disassembly 
would show the program, as if it was placed starting at 256 (&H0100) where  it 
would start, if loaded as SETKEYS.COM from CP/M Plus.

[ALT]-T  is used for transfer of memory. Just enter the original  location  of 
the start, the new location of the start, and the number of bytes to be moved.

This program is meant for experienced programmers. If you get a Mallard  BASIC 
error  message, it is your fault! If you want to study the program,  there  is 
nothing to stop you listing it.

6: Menu subroutine

In  Chapter  4,  we discussed the benefits of having  a  general-purpose  menu 
subroutine. The one below, starting at 5500, is written in DWBAS, so that  the 
boxing  in can be conveniently done. This takes a little time, so may  not  be 
desirable  in  all  circumstances. (A purpose-built routine,  or  the  use  of 
Lightning BASIC, would make it much quicker.) The first part of the program is 
the same as the one in Chapter 3.

        100 DATA Rates and Rent, Food, Clothes, Drink, Car, Fuel, Holidays
        101 Data Miscellaneous, Quit
        110 t$ = " M A I N M E N U " : j = 9
        120 RESTORE 100 : FOR n = 1 TO j : READ a$ (n) : NEXT
        130 GOSUB 5500
        140 LDW c : PRINT : PRINT "You chose "; a$ (z); "." : END
        5500 LDW c : LDW d : PRINT : l = LEN (t$) : PRINT TAB ((90 - l) /  2); 
        5510 la = 0 : FOR n = 1 TO j : lb = LEN (a$ (n)) : IF lb > la THEN  la 
= lb
        5520 NEXT
        5530  a  = 14 - j : FOR n = 1 TO j : LDW a, a + 2 * n, 38 - la /  2  : 
PRINT n ". " a$ (n) : NEXT
        5540 a = a * 8 : b = a + (2 + j) * 16 : c = 32 : d = 328
        5550 FOR n = c TO d : LDW g, n, a : LDW g, n, b : NEXT
        5560 FOR n = a TO b : LDW g, c, n : LDW g, d, n : NEXT
        5570  LDW a, 30, 33 : LDW r : PRINT " Press a key 1 to" j; : LDW  o  : 
        5580 z$ = INPUT$ (1) : z = ASC (z$) - 48 : IF z < 1 OR z > j THEN 5580
        5590 RETURN

(ROCHE> This gives:


A7: Multiple input

Program  A7 demonstrates the principle of allowing input to be entered in  the 
order  that the user, rather than the computer, chooses. It  also  illustrates 
the  idea of a simple spreadsheet. When sufficient input has been  given,  the 
computer  takes  over  and calculates what remains. If the user  has  made  an 
incorrect  entry,  use of the cursor key, followed by [<-DEL] will  delete  an 
entry.  I  agree  that this is slightly clumsy, but I  couldn't  see  anything 
better in the context of the intentions of this program.

The  formulas  that are used are well known to mathematicians  and  physicists 
but, for those of you who are 'ignorant artists', for want of a better phrase, 
they  represent the motion of a particle moving under  constant  acceleration. 
For example: dropping something off a tower, or stopping a car with a constant 
braking force. Any three of the five quantities determines the other two.

There are some interesting minor points in the program. Note the double use of 
the ON-GOTO to cover all 10 possible situations. b$ is used as an 'indicator', 
to give the current state of what has been input. It may also be  instructive, 
from the angle of error trapping. There was more to do in this way than I  had 
envisaged  when  I  started  to  write the program.  The  entry  can  give  an 
impossible  answer,  it can produce division by zero in some cases,  and  some 
entries give two possible solutions.

The program needs DWBAS loaded before running.

        10 LDW d : LDW n
        20 DATA Initial velocity, Final velocity, Acceleration, Distance, Time
        30  DIM  a$ (5), d (5) : FOR n = 1 TO 5 : READ a$ (n) : NEXT  :  b$  = 
SPACE$ (5) : c = 0
        40 LDW c : PRINT "Enter in FIGURES only. Use cursor up and down keys."
        50  FOR n = 1 TO 5 : m = 4 * n + 5 : LDW a, m, 0 : PRINT a$ (n) :  LDW 
a, m, 38 : PRINT ":" : NEXT
        60 n = 1 : LDW a, 9, 40 : WHILE c < 3 : z$ = INPUT$ (1) : z = ASC (z$)
        70 IF z = 127 AND d (n) <> 0 THEN GOSUB 350
        80 IF z = 30 OR z = 13 THEN GOSUB 360 : ELSE IF z = 31 THEN GOSUB 370
        90 IF z > 47 AND z < 58 THEN GOSUB 380
        100 m = 4 * n + 5 : LDW a, m, 40 : WEND
        110 u = d (1) : v = d (2) : f = d (3) : s = d (4) : t = d (5)
        120 ON ERROR GOTO 410
        130 i = INSTR (b$, " ") : j = INSTR (i + 1, b$, " ") : ON i GOSUB 200, 
260, 310, 340
        140 IF d1 THEN LDW a, 9, 60 : PRINT "or "; ROUND (d1, 4)
        150 IF d2 THEN LDW a, 13, 60 : PRINT "or "; ROUND (d2, 4)
        160 IF d5 THEN LDW a, 25, 60 : PRINT "or "; ROUND (d5, 4)
        170 LDW a, 5 + i * 4, 50 : PRINT ROUND (d (i), 4) : LDW a, 5 + j *  4, 
50 : PRINT ROUND (d (j), 4)
        180 ON ERROR GOTO 0 : LDW a, 29, 30 : PRINT "Another one (Y/N)?"
        190  z$ = UPPER$ (INPUT$ (1)) : IF z$ = "Y" THEN RUN : ELSE IF  z$  <> 
        200 ON j - 1 GOTO 210, 220, 230, 240
        210 IF t THEN d (1) = s / t - f * t / 2 : d (2) = s / t + f * t / 2  : 
RETURN : ELSE  ' <---- Missing 420 ?
        220 IF t THEN d (1) = s / t * 2 - v : d (3) = 2 * v / t - 2 * s / t  / 
t : RETURN : ELSE 420
        230 d (1) = v - f * t : d (4) = v * t - f * t * t / 2 : RETURN
        240 d (1) = SQR (v * v - 2 * f * s) : d1 = -d (1) : d(5) = (v - d (1)) 
/ f
        250 d5 = (v - d1) / f : RETURN
        260 ON j - 2 GOTO 270, 280, 290
        270 IF t THEN d (2) = 2 * s / t - u : d (3) = 2 * s / t / t - 2 * u  / 
t : RETURN : ELSE 420
        280 d (2) = u + f * t : d (4) = u * t + f * t * t / 2 : RETURN
        290 d (2) = SQR (u * u + 2 * f * s) : d2 = -d (2)
        300 d (5) = (d (2) - u) / f : d5 = (d2 - u) / f : RETURN
        310 ON j - 3 GOTO 320, 330
        320 IF t THEN d (3) = (v - u) / t : d (4) = (v + u) * t / 2 : RETURN : 
ELSE 420
        330  IF s THEN d (3) = (v * v - u * u) / 2 / s : d (5) = 2 * s / (u  + 
v) : RETURN : ELSE 420
        340 IF f THEN d (4) = (v * v - u * u) / 2 / f : d (5) = (v - u) / f  : 
        350  d  (n) = 0 : c = c - 1 : MID$ (b$, n, 1) = " " : LDW a, m,  40  : 
        360 n = n + 1 + (n = 5) : RETURN
        370 n = n - 1 - (n = 1) : RETURN
        380 PRINT z$; : IF MID$ (b$, n, 1) = "!" THEN c = c - 1 : PRINT SPACE$ 
(40); : LDW a, m, 41
        390 INPUT "", x$ : d (n) = VAL (z$ + x$) : MID$ (b$, n, 1) = "!" : c = 
c + 1
        400 n = (n + 1) + 5 * (n = 5) : RETURN
        410 RESUME 420
        420 LDW a, 27, 0 : PRINT "This cannot be answered." : GOTO 180

(ROCHE> This gives:


A8: Bibliography and discotheque

Probably  the  wrong  word, but it will do! There are  three  areas  in  which 
readers may wish to augment the ideas put forward in this book.


If  you  found  the  early chapters of this book difficult,  it  may  well  be 
advisable  to go back to some more elementary material on BASIC. If  you  have 
not  obtained a copy of the manual from Locomotive, you should do so.  Reports 
of this manual have been mixed -- they usually are -- but the manual does what 
it  intends. It gives faithful detail of the commands and functions  that  are 
available  in Mallard BASIC, although it probably is not ideal from the  point 
of view of the programmer without any previous experience of computers.

I  have not read more than reviews on Ian Sinclair's book. He is an author  of 
great experience, who writes for the less experienced users. His book has been 
well  reviewed, and contains a section on GSX. We have studiously avoided  GSX 
in this book, I am afraid -- it frightens me!

There  is also a "BASIC Tutorial" disc available from DW  Computronics,  which 
interacts  with  the  Amstrad PCW, so that you learn at  the  machine.  It  is 
designed  for  anyone  between complete  beginners  and  moderately  competent 

A8.2 BASIC extensions

The first Mallard BASIC extension produced commercially was EXBASIC. This  was 
produced  by  Nabitchi, but the company no longer trades.  I  understand  that 
there is an updated version, but I do not know where it is available.  EXBASIC 
was well received, and very good value for money, although slightly  ponderous 
to operate. Lightning BASIC is sold by CP Software.

A8.3 Machine code

"PCW:  Machine Code" (Spa Associates) is the only book on the market  that  is 
designed for beginners in this subject, and is also Amstrad PCW specific. This 
would  appear  to  be  the  obvious  way to  start  to  tackle  this  area  of 
programming. DW Computronics has produced an "Assembler Toolkit" and "Tutorial 
Disc". You may well find that a combination of the two provides everything you 
want for a year or two!

For  those  who wish to delve deeper into the subject, I  can  recommend  "The 
Amstrad CP/M Plus" (MML Systems). This is not bedtime reading, but it contains 
so much information that the biggest problem is finding what you want --  but, 
99% of the time, it is there! (ROCHE> This 540-pages book was commissioned  by 
Amstrad,  to  document  the version of CP/M Plus for  the  Amstrad  PCW.  When 
Amstrad  saw its size, they refused to print it... So, the authors decided  to 
self-publish it, and it was a best-seller!)

Further  details  can be obtained from: DW Computronics,  Freepost,  Chathill, 
Northumberland.  CP  Software,  Stonefield,  The  Hill,  Burford,  Oxon.   Spa 
Associates, Spa Croft, Clifford Rd., Boston Spa, W.Yorks. MML Systems, 11  Sun 
St.  London.  (ROCHE>  MML Systems is the only one still alive,  but  they  no 
longer answer messages about CP/M Plus.)

A9: Printer fonts

This  is  a big subject, and did not slot in with the other  chapters  of  the 
book,  but  it merits a brief introduction. We shall only consider  the  draft 
tables for the Amstrad PCW8256 and PCW8512. (NLQ tables and locoscript  tables 
work  on a similar system, but we shall not consider them, here.  The  Amstrad 
PCW9512 uses a different system for the daisy-wheel printer.) The hash  tables 
for  these  fonts can be obtained from block 136. They are copied  there  when 
CP/M  Plus  is  loaded, so they can also be  obtained  from  J1?CPM3.EMS.  The 
program  below searches the various versions of CP/M Plus programs, and  loads 
the tables at 40000, from where they can be inspected.

        10 MEMORY 39872!
        20 a$ = CHR$ (87) + CHR$ (1) + CHR$ (93) + CHR$ (1)
        30 f$ = FIND$ ("j*.ems") : OPEN "r", 1, f$ : FIELD 1, 128 AS b$
        40 FOR n = 1 TO 320 : GET 1, n : c$ = b$
        50 GET 1, n + 1 : d$ = b$ : c$ = LEFT$ (d$, 3)
        60 i = INSTR (c$, a$)
        70 IF i THEN 90
        80 NEXT : CLOSE : PRINT "Not found" : END
        90 b = 40001! - i : FOR m = 1 TO 10 : GET 1, n
        100 FOR k = 1 TO 128 : e$ = b$ : a = ASC (MID$ (e$, k))
        110 POKE b, a : b = b + 1 : NEXT : n = n + 1 : NEXT
        120 CLOSE : END

(ROCHE> Indented, this gives:


(No coding tricks, so it is a bit slow -- I warned you about ASC(MID$)!)

There are three tables: an address table, a binary column pattern table, and a 
table of details for each individual character. After the program is run,  the 
first table starts at 40000, the second at 40258, and the third at 40343.  The 
last  table finishes just above 41000, and is followed by a series  of  unused 
bytes filled with 255s. These bytes could be overwritten if you wished to make 
amendments to the tables.

Suppose  that  you  wish to investigate the character  that  you  print  using 
CHR$(0) (which would have to be preceded by CHR$(27), in practice). The  table 
at  40000 starts 87, 1, 93, 1. Each two bytes give the  relative  displacement 
(from  the  first  table  start,  40000)  of  the  start  of  each  character. 
40000+87+1*256=40343. We can see that 6 bytes are needed for the character, so 
we PEEK from 40343 to 40348.

For the sake of argument, let us imagine we get 15, 16, 17, 18, 19, and 20. We 
now  use the second table, and read off the binary patterns  corresponding  to 
these  numbers. Add 15 to 40258, and PEEK (40273) shows the binary pattern  of 
the first column of CHR$(0).

All  this may seem very complex, when compared with the storage of  characters 
which appear on the screen. The point of it becomes apparent when you find the 
refinements to the general system, and the space that is saved thereby.

In  the  first  table, the second byte of each displacement is  only  a  small 
number.  Hence,  the  largest four bits can be used, and are  used  for  other 
purposes. If bit 7 is set (the number will exceed 128), take 128 away to  give 
the 'correct' number. You have been signalled that the printed character  will 
be  a descended one. For instance, the printed g goes below the line.  If  the 
number  is  still greater than 16, divide by 16, and take the  remainder.  The 
result  of the division signals that the character needs this number of  blank 
columns before it starts.

In  the  third table, the entries would only reach 85 (unless you  add  a  few 
more).  Setting bit 7 can be used as a signal. It means print a blank  column, 
then deal with the remainder. The numbers 123-127 also have special  meanings, 
and are used to signal repeat printings of the column pattern that follows.

Imagine  that you would like to change the @ character to a square root  sign. 
First, design your character. Turn it into binary column patterns, and see  if 
they  exist in the list from 40258-40342. If they don't, you will have to  add 
them.  Suppose  that you need two extra patterns. You will have  to  move  the 
third table up two bytes, to make room. Then, include the two new patterns  at 
40343-40344.  Every relative address in table 1 will have to be  increased  by 
two bytes. You now replace the coding for @ by your new coding. Hopefully,  it 
is  not longer -- if so, more problems. If it is shorter, you can pad it  with 
0s. All that remains is to write a reverse of program A9 to rewrite your  CP/M 
Plus.  At this stage, you are probably feeling that this is far too  difficult 
an operation to try -- I don't blame you in the least! All I wanted to do  was 
to show that, like most things, it COULD be done from Mallard BASIC.

10: LNER 'Mallard' listing

Our Mallard locomotive engine picture appearing on the cover of this book  was 
drawn using Locomotive Software's Mallard BASIC with CP Software's  'Lightning 
BASIC' extension.

        10 LEB xm : LEB d : LEB gh : LEB c : LEB l, 20, 175, 180, 175
        20 LEB l, 180, 175, 200, 170 : LEB l, 200, 170, 670, 170
        30 LEB l, 140, 99, 660, 99
        40 FOR n = 1 TO 3 : LEB i, 580 + 4 * n, 99 - n, 660, 99 - n : NEXT
        50 LEB l, 120, 175, 100, 109 : LEB l, 100, 109, 112, 104 : LEB l, 112, 
104, 140, 99 : LEB l, 660, 99, 660, 175
        60 LEB f, 146, 170
        70  LEB l, 100, 109, 100, 96 : LEB l, 100, 96, 140, 96 : LEB  l,  140, 
96, 140, 99
        80 FOR n = 98 TO 108 STEP 2 : LEB l, 100, n, 140, n : NEXT
        90 LEB gm
        100  DATA 40, 180, 10, 80, 180, 10, 140, 165, 25, 192, 165,  25,  244, 
165, 25, 300, 180, 10
        110 FOR nn = 1 TO 6 : GOSUB 290 : NEXT
        120 LEB gh : LEB xl, 580, 99, 580, 160 : LEB xl, 580, 160, 660, 170
        130  LEB xl, 580, 160, 440, 145 : LEB xl, 440, 145, 70, 145 : LEB  xl, 
70, 145, 40, 160
        140 LEB a, 17, 73 : LEB r : PRINT "4 4 6 8"
        150  LEB  o : LEB gh : FOR x = 590 TO 620 : LEB xl, x, 109, x,  127  : 
        160 FOR x = 624 TO 640 : LEB xl, x, 109, x, 127 : NEXT
        170 LEB xl, 135, 110, 205, 110 : LEB xl, 135, 121, 135, 110
        180 LEB xl, 135, 121, 205, 121 : LEB xl, 205, 110, 205, 121
        190 LEB r : LEB a, 14, 18 : PRINT "MALLARD" : LEB o
        200 LEB xl, 120, 105, 580, 105
        210  LEB  l, 15, 175, 20, 175 : LEB l, 15, 170, 15, 177 : LEB  l,  16, 
170, 16, 177 : LEB l, 17, 173, 17, 177
        220 FOR m = 175 TO 182 : FOR n = 20 TO 660 STEP 4 : LEB p, n, m : NEXT 
        230  FOR  m = 170 TO 175 : FOR n = 180 TO 660 STEP 4 : LEB p, n,  m  : 
        240  FOR  n = 1 TO 1000 : r = ROUND * 900 : s = SQR (r) : r2 =  RND  * 
        245 s2 = SQR (r2) : LEB p, 225 - s2, 64 + s : NEXT
        250 PRINT CHR$ (7)
        260 z$ = "" : WHILE z$ = "" : z$ = INKEY$ : WEND
        270 IF UPPER$ (z$) = "S" THEN LEB sd
        280 END
        290 p = 3.141593 : READ x, y, r
        300  LEB gm : LEB zc, x, y, r : LEB zc, x, y, r + 1 : IF r >  20  THEN 
LEB zc, x, y, r - 1
        310 st = p / 16 : IF r < 20 THEN st = p / 8
        320 FOR n = 0 TO 2 * p + 0.001 STEP st
        330 LEB l, x - r * COS (n), y - r * SIN (n) * 0.9, x + r * COS (n),  y 
+ r * SIN (n) * 0.9
        340 NEXT : RETURN

A11: Notes to the second edition

A  second  edition of a book should be a good sign. Both the  author  and  the 
publisher must remain keen about it. Streamlined has produced a fair amount of 
comment, most of it favourable, and the extras we put into the new edition are 
mainly a result of readers' suggestions. Among many others, I am  particularly 
grateful  to George Bridge, who has given me various ideas for improvement  of 
the disc editor, and Chris Shipp, whose reaction to the book has been  similar 
to Oliver Twist's reaction to food.

There  are a few minor amendments to the text, and an index -- my  thanks  are 
due  to  readers  who provided some excellent ones of  their  own.  The  major 
changes,  however, consist of extra programs which are provided on the B  side 
of the disc. All of them need DWBAS (or DW139).

Of all the new programs that I wrote for Streamlined, the one I have used most 
is  C18P3,  the  disc editor. I came to love it on a  morning,  when  disaster 
struck.  I  had spent about an hour working on a long program,  and  dutifully 
SAVEd  the update. The Midlands Electricity Board, with precise timing,  chose 
the  instant for an electricity cut when the directory of the old version  had 
been  erased and the directory for the new version had not been  written.  The 
chances  of  this must be pretty small, but that's the sort of  luck  I  have! 
C18P3 passed this test with flying colours, and all was recovered.

The original intention of the program was simply to show that it was  possible 
to  write a disc editor in Mallard BASIC, rather than to suggest that  it  was 
comparable  in  quality to other commercial editors. As I used it, I  came  to 
realise  that,  while it did some things well, other aspects were  clumsy  and 
more options would be useful. BCDE, on the B side of the disc, is the  result. 
The additions are described on the extra HELP pages, but readers will see that 
it  is developed from C18P3 which is, of course, fully documented in the  text 
of Chapter 18.

A criticism that was made of the first edition concerned the lack of  diagrams 
and  screen  dumps  in the graphics section. I am  dubious  whether  this  was 
altogether  fair,  since  there were a number of  programs  in  that  section. 
Readers could run these and create their own pictures. However, I would accept 
that  the  attitude  of the chapter was: "Here is the theory, now  go  on  and 
develop it yourself."

DRAWDEMO (and DRAWSUB.DOC) are an attempt to answer this. DRAWDEMO can  simply 
be  run  as  a  mild form of entertainment, to demonstrate  a  wide  range  of 
graphics  effects that can be obtained on the Amstrad PCW. Some of  these  are 
very  quick,  others are more detailed. The more adventurous readers  will  be 
able  to go further, and extract the routines from this program to write  into 
their own files.

We  conclude with three more programs, of a lighter nature. These  are  simply 
meant  to  be  fun and, if you don't agree, well, don't agree!  They  do  also 
contain some programming tricks which may be of interest to some readers.

Chris  Shipp  showed  me a neat technique for creating a  sequence  of  random 
numbers  that does not repeat itself. This is obviously a necessary part of  a 
programmer's  armoury. For example, when you write a Quiz program,  you  don't 
want  a  question to repeat itself. The program SNOW was developed  from  this 
algorithm.  It  also illustrates the use of the character set and OUT  246  to 
produce interesting effects.

Liverpool  (who  are  top of the Footbal League  table)  would  probably  beat 
Milwall (who are bottom) if they played them to-day. But, once in a while, the 
'wrong'  result happens. To simulate this using Poisson variables --  and,  if 
you don't know and don't care what these are, it doesn't matter in the least -
-  is  the  only serious part of the program SOCCER. Maybe  you  can  use  the 
program to keep the children out of trouble on a wet Sunday afternoon. If  you 
use it for pools prediction, don't blame me.

Lastly,  CRAZYWP.  Appendix  9 discusses altering printer  characters  on  the 
Amstrad PCW8256/PCW8512. You may have read it, and come to the conclusion that 
this was far too difficult to achieve in Mallard BASIC (or any other language, 
for that matter). CRAZYWP demonstrates how this can be done, but I don't think 
that it will make anybody abandon Locoscript!

The  latest  version  of BCDE is contained in two programs,  as  the  numerous 
additions made it too long when a B drive was being examined. The second  file 
is  called MAP.BAS, which does not run on its own. The best method to  operate 
BCDE  is  to PIP both files to the M disc, before running  Mallard  BASIC  and 
DWBAS. Then, RUN"M:BCDE". If the files have not been copied already, BCDE will 
do this when it is run. Subsequently, changes can be made smoothly between the 
mapping and the other operations.


(To be done by WS4.)

(Listing of un-protected RPED ?)