PCWsBAS.WS4
-----------
- "PCW: Streamlined BASIC"
Geoffrey Childs
PCW-World, 1990
A comprehensive guide to streamlining Mallard BASIC on the Amstrad PCW range
(Retyped by Emmanuel ROCHE.)
COPYRIGHT (C)
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
publishers.
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.
Acknowledgements:
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
------
Introduction
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.2 PRINT and LPRINT
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.2 ON ERROR GOTO
6.3 Long or short lines?
6.4 Protection and OPTION RUN
6.5 Where does one put subroutines?
6.6 GOTO taboo?
6.7 PRINT
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.5 INSTR
7.6 CHR$ and ASC
7.7 PEEK, POKE, CALL, and USR
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.5 SCRRUN
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
Appendices
----------
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
Index
Part 1
------
Introduction
------------
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
pages.
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
specific?
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
VARPTR?
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
bugs.
'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
program.
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
like
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
write
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.
2.2 PRINT and LPRINT
--------------------
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
COMMON before the CHAIN MERGE.
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#
: WEND : RETURN
(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:
C2P1
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.
Consider:
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:
1111111111111111
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
XOR.
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
(1).
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
needed.
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
instance).
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
found?"
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:
C3P1
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
200
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
toggle.
3.5 Checking input
------------------
C3P2
10 INPUT "Please enter your name: ", a$
20 PRINT : PRINT "Is your name "; a$; "? Press Y for YES, and N for
NO."
30 z$ = UPPER$ (INPUT$ (1)) : IF z$ = "N" THEN 10 : ELSE IF z$ <> "Y"
THEN 30
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
advantage.
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:
C3P3
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
menus!
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$
WEND
CLOSE
)
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
solution:
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
: WEND
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.
C4P1
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$ :
PUT 1
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
program:
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
respect.
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
AUTO.
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
amendments:
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.).
6.2 ON ERROR GOTO
-----------------
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
mistakes.
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
characters.
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)-
4(BILLS.AGE-7)=60...!?
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
this.
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.
C6P2
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); :
NEXT
(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.
C6P3
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.
6.7 PRINT
---------
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$
WEND
)
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$
("ACCOUNTS.").
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.
C7P1
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.
C7P2
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.
7.5 INSTR
---------
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!
C7P3
200 a$ = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC"
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,
eventually.
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.
7.7 PEEK, POKE, CALL, and USR
-----------------------------
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$.
C8P1
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
found.
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.
C8P2
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
naturally..."
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
book.
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'
BASIC.
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.
C9P1
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#,
3)
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?
Vanity?)
C9P2
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.
C9P3
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 =
31529
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
number."
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
finished."
830 FOR n = 0 TO 7 : k (n) = 0 : NEXT : RETURN
890:
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.
C9P4
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
programs.
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
socket.
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
interpreter.
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.
(ROCHE> So,
******** 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
---
201
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
clearer.)
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.)
C11P1
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.
C11P2
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.
C11P3
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.
C11P4
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.
C11P5
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
patterns
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 *
256
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
conclusions:
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...
------------------------------------
C12P1
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 -
1
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
: NEXT
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
this!
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
language.
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
documentation?)
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"!)
C13P1
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,
INC HL, LD (HL) 0
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
memory.
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.
C13P2
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
File".).
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.
C13P3
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)); :
NEXT : PRINT ".";
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
KiloBytes.
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
extension.
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.
C13P4
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
program.
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
128.
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
problems!
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
"Return_Integer".)
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
called.
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:
C14P1
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.
The jumps are made to SYSTEM RESET, SCAN KEYBOARD, SCAN AND WAIT FOR KEYPRESS,
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.
C14P2
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
h
(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
interest.
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
included).
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
PCW9512.
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
answers!
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
233 in block 128. ALL THE CALLS IN THIS JUMP BLOCK WORK IN EVERY VERSION OF
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.)
C15P2
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
PCW.
C15P3
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:
$$$$
)
C15P4
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.
C16P1
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,
201
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.
C16P2
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!
C17P1
10 h = HIMEM : j = INT ((h + 1) / 256) - 1 : k = 256 * j : MEMORY k -
1
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,
159
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
them!"
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
areas.
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.
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.
C18P2
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 :
CALL k
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
k
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
stop."
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.
Plagiarism!
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.
C18P3
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
(s$)
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
d$;
156 GOSUB 800 : RETURN
200 GOSUB 700 : GOSUB 720 : GOSUB 1602 : z$ = INPUT$ (1) : q = ASC
(z$)
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
program."
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 :
RETURN
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); :
RETURN
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,
221
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$;
: NEXT
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.
Or:"
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
operate.)"
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.
Re-enter."
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 :
RETURN
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
satisfactory.
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
above!
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
----------------------
C19P1
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!
Appendices
----------
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
comment.)
#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.
A4
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
A4139
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.
A6
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);
t$
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 :
LDW e
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.
A7
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$ <>
"N" THEN 190 : ELSE END
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 :
RETURN : ELSE 420
350 d (n) = 0 : c = c - 1 : MID$ (b$, n, 1) = " " : LDW a, m, 40 :
PRINT SPACE$ (40) : RETURN
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.
A8.1 BASIC
----------
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
programmers.
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.
A9
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.
A10
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 :
NEXT
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
: NEXT
230 FOR m = 170 TO 175 : FOR n = 180 TO 660 STEP 4 : LEB p, n, m :
NEXT : NEXT
240 FOR n = 1 TO 1000 : r = ROUND * 900 : s = SQR (r) : r2 = RND *
14400!
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.
Index
-----
(To be done by WS4.)
(Listing of un-protected RPED ?)
EOF