Автор: Simon N. Goodwin
Год: 1984
Издатели: Your Spectrum
Языки:
Английский
Формат:
TAP лента
Требования:
ZX Spectrum 48K
Ссылки:
Страница на ZXArt
Страница на World Of Spectrum
Страница на Spectrum Computing
Скриншоты:
Год: 1984
Издатели: Your Spectrum
Языки:
Формат:
Требования:
Ссылки:
Скриншоты:
!0.......^.........^.........^..
!B
\H11\H07\H10\H02 SMOOTH MOVES
\H11\H01
!2.......^.........^.........^.........^.........^.........^....
Does your Speccy output sometimes look like it's suffering from
a bad case of the shakes? Relax ... let things slide with Simon
Goodwin's cure for jerky graphics!
!1.......^.........^.........^.........^........
Presented here is a short machine code routine
that lets you move graphics smoothly around the
screen, without suffering the restrictions of
the Spectrum character grid. Graphics and ASCII
characters can be positioned at any high res-
olution coordinate with a simple Basic command.
You're not restricted to the 704 'PRINT pos-
itions' - rows zero to 21 and columns zero to 31
- you can place characters at any point on the
256 by 176 Hi-res grid.
The program uses only 120 bytes of memory and
is completely relocatable, which means that you
can load it anywhere in memory. It works without
problems on a 16K computer. Another advantage
over the usual PRINT AT command is that this one
allows you to use an extra ninety-odd user-
defined graphics. In addition to the usual 21
user-defined graphics (character codes 144-164),
you can define and position characters 165 to
255 with the YS Smooth Move routine.
!0.......^.........^.........^..
!B
INTO LOAD MODE
!1.......^.........^.........^.........^........
The Basic listing loads the machine code into
any area of memory. Type in the listing, taking
care over the DATA statements, and then decide
where you wish to store the code. On a 48K Spec-
trum you might want to put the code at address
64500. Type CLEAR 64499 to tell Basic that it
must not use addresses above 64499, and then RUN
the program. You'll be asked for a "Load
address" - enter 64500. The program reads the
DATA and stores it from memory address 64500
onwards.
If you've made a typing error in the DATA, an
appropriate message will appear. Correct the
error and RUN again. Don't test the routine
until the "Position character ..." message
appears; the code is then ready for use. It's a
good idea to SAVE everything, just in case an
error has slipped past the 'check' in the
loader.
On a 16K Spectrum (assuming you have no other
machine code in memory) you might put the code
at address 31670. Type CLEAR 31669 and then
specify a "Load address" of 31670. Of course,
you can load the code anywhere you like,
although it's best to protect it with the CLEAR
command, or it could be overwritten by Basic.
The routine provides a Hi-res version of the
command:
!0.......^.........^.........^..
PRINT INK 8; PAPER 8; OVER 1;
AT y,x;CHR$ c
!1.......^.........^.........^.........^........
The syntax is rather different:
!0.......^.........^.........^..
RANDOMIZE x AND y=c+USR a
!1.......^.........^.........^.........^........
Where 'x' and 'y' are the horizontal and vert-
ical coordinates of the top left corner of the
character, 'c' is the ASCII code of the char-
acter and 'a' is the address where you stored
the routine. So:
!0.......^.........^.........^..
RANDOMIZE 0 AND 175=65+USR
64500
!1.......^.........^.........^.........^........
will position a letter 'A' (character 65) at the
top left corner of the screen, assuming you
stored the machine code at address 645000. The
RANDOMIZE is a 'dummy' to hold the result of the
USR call. If your program uses random numbers
you should replace RANDOMIZE with a dummy
variable assignment, such as:
!0.......^.........^.........^..
LET dummy=0 AND 175=65+USR
64500
!1.......^.........^.........^.........^........
You're allowed to specify coordinates or char-
acter codes with expressions as well as vari-
ables or numbers. For instance:
!0.......^.........^.........^..
RANDOMIZE xpos+xdir AND ypos-
ydir=CODE "*"+USR move
!1.......^.........^.........^.........^........
is allowed. Our routine uses a neat technique to
fetch the three previous expressions on the
line, before the USR call. If there are more or
less than three values, a 'Parameter' error will
be reported. Make sure that you've used the
correct separators - AND, equals and plus -
between the coordinates, the character code and
the USR call. If you're using calculations more
complicated than addition, subtraction, multi-
plication and division, you may need to put each
coordinate or character code in brackets - so
that the routine can distinguish them.
The machine code contains extensive error
trapping. It won't let you use y coordinates
less than seven, since each character is eight
lines high. A character at coordinates 0,6 would
have its bottom line at y coordinate minus one!
The "Integer out of range" error message appears
if you use vertical coordinates less than seven
or greater than 175. X coordinates beyond 248
simply "wrap around" to the opposite side of the
screen; decimal values are rounded to the
nearest whole number.
!0.......^.........^.........^..
!B
A SMOOTH OPERATOR
!1.......^.........^.........^.........^........
The second Basic program shows the features of
the routine quite clearly. A ball bounces around
the screen at a variety of speeds. It's pos-
itioned using "LET d=" rather than "RANDOMIZE"
so that the random number sequence is not
constantly re-started whenever the ball moves.
The listing is fairly straightforward, but make
sure you type commas (not semi-colons) in line
330.
The machine code always uses the OVER 1
setting, so that any character can be erased
without destroying the background, simple by re-
drawing it in the same place. The characters
take on the colour of the INK where they are
plotted. This avoids the need for complicated
code to save and restore colours, and prevents
weird effects as objects pass one another.
The Spectrum normally uses character codes 165
to 255 to represent keywords - words like THEN,
PRINT and so on. There's never any need to zoom
those around the screen, so our machine code
program lets you define an extra 91 user-
defined graphics in their place. Together with
the 21 standard user-defined graphics, this
gives you 122 characters to play with.
!0.......^.........^.........^..
!B
--------------------------------
LABELS |ADDRESS |COMMENT
--------------------------------
| |SYSTEM POINTERS
--------------------------------
UDGS | 5C7B |User graphics
| |pointer
CHARS | 5C36 |Character set
| |pointer
BLKCH | 5C92 |Block graphic
| |buffer
STACK | 5C65 |Maths stack end
| |pointer
STBOT | 5C63 |Maths stack
| |start pointer
--------------------------------
| |ROM ROUTINES
--------------------------------
MAKEB | 0B38 |Make graphic
| |in B
PIXEL | 22AA |Find address of
| |pixel
POP_A | 2DA2 |Pop A from
| |maths stack
--------------------------------
!2.......^.........^.........^.........^.........^.........^....
The table above shows the system pointers and ROM routines used
in Smooth Move, giving their labels and addresses. This is for
the assembler's use only, and need not be typed in.
!1.......^.........^.........^.........^........
!B
In principle the new characters are defined in
exactly the same way as the old ones. They
follow the others in memory, which means that
you will have to expand the user-defined
graphics area before you can define more than
the standard 21 characters. On a 48K computer
you'd use the following commands to expand the
graphics area to cope with 122 characters:
!0.......^.........^.........^..
CLEAR 64559: POKE 23675,48:
POKE 23676,252
!1.......^.........^.........^.........^........
The POKEs adjust the system variable UDG so that
it points an extra 728 (91*8) bytes further down
memory. They don't reserve memory for any
machine code, so you'd probably use CLEAR 64499
and load the Smooth Move code at 64500 (or
thereabouts).
On a 16K computer load the machine code at
31670 and reserve space with:
!0.......^.........^.........^..
CLEAR 31669: POKE 23675,48:
POKE 23676,124
!1.......^.........^.........^.........^........
When you come to program the user-defined
graphics, you POKE the patterns into memory as
usual. The only difference is that you can't use
the USR "letter" function to locate characters
after USR "u". The extra characters still follow
at eight-byte intervals. User-defined graphic
"a" has character code 144, so you can find the
definition of the character with code 'n' by
typing:
!0.......^.........^.........^..
PRINT USR "a"+8*(n-144)
!B
TRICKS OF THE TRADE
!1.......^.........^.........^.........^........
Smooth Move uses some interesting machine code
tricks, so I've listed the assembler code of the
program as well as the Basic loader. This is the
longest listing, assembled using version 2.1 of
Picturesque's excellent EDITAS assembler.
!0.......^.........^.........^..
100 REM SMOOTH MOVE DEMO
110 REM By Simon N Goodwin
120 REM
130 REM Load code
140 CLEAR 30999
150 LOAD "Mover"CODE 31000
160 REM Set area
170 LET xmax=247
180 LET ymax=168
190 REM Set up positions
200 LET xpos=INT (RND*200)
210 LET ypos=17
220 LET xdir=INT (RND*6+1)
230 LET ydir=-INT (RND*5+1)
240 REM Define ball
250 RESTORE
260 FOR i=USR "a" TO USR "e"
270 READ d
280 POKE i,d
290 NEXT i
300 LET shape=144
310 INK 6: PAPER 2: BORDER 5
320 FOR i=0 TO 21
330 PRINT AT i,0, INVERSE 1,
350 NEXT i
360 GO TO 480
390 REM Move ball
400 LET oldx=xpos
410 LET oldy=ypos
420 LET xpos=xpos+xdir
425 IF xpos>1 THEN IF xpos<xma
x THEN GO TO 440
430 LET xpos=oldx: LET xdir=INT
(RND*7+1)*-SGN xdir
435 BEEP .03,15
440 LET ypos=ypos+ydir
445 IF ypos>7 THEN IF ypos<yma
x THEN GO TO 460
450 LET ypos=oldy: LET ydir=INT
(RND*5+1)*-SGN ydir
455 BEEP .03,30
460 LET d=oldx AND oldy=shape+U
SR 31000
470 LET shape=shape+1-4*(shape=
147)
480 LET d=xpos AND ypos=shape+U
SR 31000
490 GO TO 400
590 REM Ball definition
600 DATA 60,66,135,143,143,159,
126,60
610 DATA 60,66,193,225,249,253,
126,60
620 DATA 60,126,249,241,241,225
,66,60
630 DATA 60,126,191,159,135,131
,66,60
640 DATA 0
!2.......^.........^.........^.........^.........^.........^....
A demonstration program featuring a ball bouncing around the
screen at a variety of speeds. (Make sure you type commas and
not semi-colons in line 330.)
!1.......^.........^.........^.........^........
!B
The assembler listing starts with the defin-
ition of a few constants used later in the
program. These are defined at the head of the
listing so that they can be checked and altered
easily. They also make the program easier to
read, especially if it doesn't immediately
spring to mind that, say, 5C65H is the address
of the Maths stack pointer! Three ROM routines
have been used, to minimise the program size and
keep things simple.
The machine code is in three main sections.
First, the parameters (the coordinates and char-
acter number) are checked, then the character
definition is located, and finally the character
is positioned on the screen. The parameter
checking relies on the way the Spectrum eval-
uates expressions. In a simple sum like:
!0.......^.........^.........^..
PRINT 2+3*4
!1.......^.........^.........^.........^........
the computer must work out the multiplication
before it does the addition. This is because the
correct answer is 14 (or 2+12), not 20 (5*4).
The Smooth Move code takes advantage of the
way Basic works out the result of a calculation,
by ensuring that the coordinates and character
number have been worked out, but not combined
together, as the USR call is performed. The
three numbers are languishing on a "maths stack"
of temporary results during the USR call. The
machine code can read the numbers which have
been so helpfully made ready by Basic, and then
put things tidy afterwards so that Basic can
carry on once the USR call is over.
!0.......^.........^.........^..
100 REM SMOOTH MOVE LOADER
110 REM By Simon N Goodwin
120 REM
200 INPUT "Load address";l
210 LET c=0
220 FOR i=l TO l+119
230 READ d
240 LET c=c+d
250 POKE i,d
260 NEXT i
270 IF c<>13017 THEN PRINT "Er
ror in DATA": STOP
280 PRINT "Position character c
AT x,y with"
290 PRINT "RANDOMIZE x AND y =
c +USR ";l
300 PRINT '"Save everything, ju
st in case..."
310 SAVE "SMOOTH/BAS"
320 SAVE "SMOOTH/COD"CODE l,120
330 STOP
400 DATA 42,101,92,229,235,42
410 DATA 99,92,1,15,0,9
420 DATA 237,82,40,2,207,25
430 DATA 205,162,45,254,128,56
440 DATA 11,71,214,144,56,19
450 DATA 237,91,123,92,24,4
460 DATA 237,91,54,92,38,0
470 DATA 111,41,41,41,25,24
480 DATA 6,205,56,11,33,146
490 DATA 92,229,221,225,205,162
500 DATA 45,103,229,205,162,45
510 DATA 225,111,229,14,8,225
520 DATA 37,229,36,197,68,77
530 DATA 205,170,34,193,71,175
540 DATA 176,221,126,0,40,17
550 DATA 235,38,0,111,62,8
560 DATA 144,71,41,16,253,235
570 DATA 126,170,119,35,123,174
580 DATA 119,221,35,13,32,213
590 DATA 225,225,34,101,92,201
!2.......^.........^.........^.........^.........^.........^....
If you've not yet got hold of an assembler, here's a Basic
loader program allowing you to load the Smooth Move.
!1.......^.........^.........^.........^........
!B
This gives us a convenient way of passing
numbers from Basic to machine code, without the
hassle of PEEKing and POKEing. We can ensure
that temporary results are ready by our choice
of separators between the coordinates. In the
command:
!0.......^.........^.........^..
RANDOMIZE x AND y=c+USR a
!1.......^.........^.........^.........^........
Basic must do the addition first, to get the
correct answer - adding is always done before
comparison (=) and comparisons are done before
AND. This idea is explained on page 12 of the
thin "Introduction" manual which came with your
Speccy.
On the way through the expression, Basic works
out any calculations needed to find x, y and c,
since those calculations should have a higher
priority than the 'plus' at the end of the line.
If you want to use equals, AND, or other so-
called "logical operations" in your calculation
of x, y and c, you must put the relevant
calculations in brackets, so that Basic will
work out the whole value before it reaches the
USR call. A list of priorities is on page 201 of
the Spectrum manual. Logical operations have
priority '2', '3', '4' or '5'.
Basic puts temporary results in an area called
the "maths stack". Two system variables are used
to mark the top and bottom of this area; each
value stored within it takes up five bytes.
Lines 1340-1450 of the assembler are used to
check that the maths stack is 15 bytes long when
the USR call is reached; this means three values
are ready. If the stack does not contain three
values, the routine stops with a 'Parameter'
error - this is generated by lines 1440 & 1450.
!0.......^.........^.........^..
!B
A CHARACTER STUDY
!1.......^.........^.........^.........^........
The ROM subroutine POP_A is used to read a
number from the maths stack and into the A
register. Lines 1470-1560 fetch the character
code, which is the last thing calculated and
hence at the 'top' of the stack. They test the
code to decide whether the character is a block
graphic, a user-defined graphic or an ASCII
character. There's no definition of the block
graphics in the ROM - those are generated as
required by a routine called MAKEB, which puts
the pattern specified by a code in the B regis-
ter at address BLKCH.
In the case of ASCII or user-defined charac-
ters, the routine finds the appropriate start
address from the system variables (UDGS points
to the user-defined graphics and CHARS points to
the ASCII symbols). The character code is multi-
plied by eight (since each definition takes
eight bytes) and the location of the character
is found by adding the start address to the
resultant value. GFONT copies the address of the
character definition into register IX, for safe-
keeping.
POP_A is used twice more to fetch the y and x
coordinates where the character is to be
displayed. The loop from PLINE onwards puts the
character into video memory, one line at a time.
Register C is used to count the lines.
The program takes the coordinates of each line
and uses a ROM call to find the address where
that line should appear. The ROM subroutine
named PIXEL takes x and y coordinates in regis-
ters C and B, returning with the address of the
byte required in HL and the position within the
byte in A. The character definition is fetched
and shifted sideways if need be.
Each line of the Spectrum display corresponds
to 32 bytes of video memory. The contents of
that memory determines what's displayed on the
line. The normal Spectrum PRINT routine uses one
byte per character on each line, which means
that characters cannot be printed partly in one
byte and partly in the next. Smooth Move allows
you to split characters between one byte and the
next (hence the finer control over positioning).
The character code is put into one end of the HL
register pair, which is shifted sideways until
it's at the required place on the boundary
between H and L. The ADD Hl,HL instruction is
used to shift the value - every time you add a
binary value to itself it moves one place to the
left, because each column has twice the value of
the one to its right.
At STORE the graphic line is mixed into the
display with the XOR instruction - the machine
code equivalent of PRINT OVER. The program loops
back to PLINE until all eight lines of the
character have been positioned.
!0.......^.........^.........^..
!B
TIDYING UP
!1.......^.........^.........^.........^........
The routine can't return to Basic until both
stacks - the processor stack and the maths stack
- have been put back the way they were found.
Line 2330 throws away the coordinate information
which was on the processor stack. Finally, the
value of the maths stack pointer is retrieved,
so that Basic doesn't get confused by the sudden
loss of three data items. The USR function
returns the value zero, since B and C have both
counted down to nothing.
This program is only an introduction to Hi-res
animation on the Spectrum. The best graphic
routines handle large shapes, with automatic
animation and motion, collision detection, and
so forth. One day I might divulge the secrets of
the YS Sprite System, which puts most of the
power of a dedicated arcade machine at your
fingertips - that's if I ever finish writing it
...!
!2.......^.........^.........^.........^.........^.........^....
!B
The assembler code for Smooth Move - just so that you can see
the interesting machine code tricks that Simon's used.
FDE8 1320 ORG 65000
1330 ; "Fetch end of stack"
FDE8 2A655C 1340 MOVER LD HL,(STACK)
FDEB E5 1350 PUSH HL
1360 ; "3 numbers on stack?"
FDEC EB 1370 EX DE,HL
FDED 2A635C 1380 LD HL,(STBOT)
FDF0 010F00 1390 LD BC,15
FDF3 09 1400 ADD HL,BC
FDF4 ED52 1410 SBC HL,DE
FDF6 2802 1420 JR Z,FCODE
1430 ; "3 parameters needed!"
FDF8 CF 1440 RST #08
FDF9 19 1450 DEFB 25
1460 ; "Find the char. code"
FDFA CDA22D 1470 FCODE CALL POP_A
1480 ; "Divide into 3 groups:"
1490 ; " 0-127 ASCII chars"
1500 ; "128-143 block graphics"
1510 ; "144-255 user defined"
FDFD FE80 1520 CP 128
FDFF 380B 1530 JR C,ASCII
FE01 47 1540 LD B,A
FE02 D690 1550 SUB 144
FE04 3813 1560 JR C,BLOCK
1570 ; "Must be a UDG"
FE06 ED5B7B5C 1580 LD DE,(UDGS)
FE0A 1804 1590 JR INDEX
1600 ;
FE0C ED5B365C 1610 ASCII LD DE,(CHARS)
1620 ; "Find character form"
FE10 2600 1630 INDEX LD H,#00
FE12 6F 1640 LD L,A
FE13 29 1650 ADD HL,HL
FE14 29 1660 ADD HL,HL
FE15 29 1670 ADD HL,HL
FE16 19 1680 ADD HL,DE
FE17 1806 1690 JR GFONT
1700 ;
FE19 CD380B 1710 BLOCK CALL MAKEB
FE1C 21925C 1720 LD HL,BLKCH
FE1F E5 1730 GFONT PUSH HL
FE20 DDE1 1740 POP IX
1750 ; "Fetch Y coordinate"
FE22 CDA22D 1760 CALL POP_A
FE25 67 1770 LD H,A
1780 ; "Fetch X coordinate"
FE26 E5 1790 PUSH HL
FE27 CDA22D 1800 CALL POP_A
FE2A E1 1810 POP HL
FE2B 6F 1820 LD L,A
FE2C E5 1830 PUSH HL
1840 ; "Process 8 screen lines"
FE2D 0E08 1850 LD C,8
FE2F E1 1860 PLINE POP HL
1870 ; "Step up to next line"
FE30 25 1880 DEC H
FE31 E5 1890 PUSH HL
FE32 24 1900 INC H
1910 ; "Convert coord in H,L"
1920 ; "to address in HL & A"
FE33 C5 1930 PUSH BC
FE34 44 1940 LD B,H
FE35 4D 1950 LD C,L
FE36 CDAA22 1960 CALL PIXEL
FE39 C1 1970 POP BC
1980 ; "Copy bit offset to B"
FE3A 47 1990 LD B,A
2000 ; "See if char is on grid"
FE3B AF 2010 XOR A
FE3C B0 2020 OR B
2030 ; "Read font anyway"
FE3D DD7E00 2040 LD A,(IX+0)
2050 ; "Store NOW if on grid"
FE40 2811 2060 JR Z,STORE
2070 ; "Generate 16 bit mask"
FE42 EB 2080 EX DE,HL
FE43 2600 2090 LD H,0
FE45 6F 2100 LD L,A
2110 ; "Reverse shift count"
FE46 3E08 2120 LD A,8
FE48 90 2130 SUB B
FE49 47 2140 LD B,A
FE4A 29 2150 SHIFT ADD HL,HL
FE4B 10FD 2160 DJNZ SHIFT
2170 ; "Put mask in DE"
FE4D EB 2180 EX DE,HL
2190 ; "Mix into display"
FE4E 7E 2200 LD A,(HL)
FE4F AA 2210 XOR D
FE50 77 2220 LD (HL),A
FE51 23 2230 INC HL
FE52 7B 2240 LD A,E
FE53 AE 2250 STORE XOR (HL)
FE54 77 2260 LD (HL),A
2270 ; "Advance through font"
FE55 DD23 2280 INC IX
2290 ; "Count one line done"
FE57 0D 2300 DEC C
FE58 20D5 2310 JR NZ,PLINE
2320 ; "Tidy stacks; n.b. BC=0"
FE5A E1 2330 POP HL
FE5B E1 2340 POP HL
FE5C 22655C 2350 LD (STACK),HL
FE5F C9 2360 RET
2370 END
!1.......^.........^.........^.........^........
!B
--
from Your Spectrum #7 (Sep.1984)
--
!$
!B
\H11\H07\H10\H02 SMOOTH MOVES
\H11\H01
!2.......^.........^.........^.........^.........^.........^....
Does your Speccy output sometimes look like it's suffering from
a bad case of the shakes? Relax ... let things slide with Simon
Goodwin's cure for jerky graphics!
!1.......^.........^.........^.........^........
Presented here is a short machine code routine
that lets you move graphics smoothly around the
screen, without suffering the restrictions of
the Spectrum character grid. Graphics and ASCII
characters can be positioned at any high res-
olution coordinate with a simple Basic command.
You're not restricted to the 704 'PRINT pos-
itions' - rows zero to 21 and columns zero to 31
- you can place characters at any point on the
256 by 176 Hi-res grid.
The program uses only 120 bytes of memory and
is completely relocatable, which means that you
can load it anywhere in memory. It works without
problems on a 16K computer. Another advantage
over the usual PRINT AT command is that this one
allows you to use an extra ninety-odd user-
defined graphics. In addition to the usual 21
user-defined graphics (character codes 144-164),
you can define and position characters 165 to
255 with the YS Smooth Move routine.
!0.......^.........^.........^..
!B
INTO LOAD MODE
!1.......^.........^.........^.........^........
The Basic listing loads the machine code into
any area of memory. Type in the listing, taking
care over the DATA statements, and then decide
where you wish to store the code. On a 48K Spec-
trum you might want to put the code at address
64500. Type CLEAR 64499 to tell Basic that it
must not use addresses above 64499, and then RUN
the program. You'll be asked for a "Load
address" - enter 64500. The program reads the
DATA and stores it from memory address 64500
onwards.
If you've made a typing error in the DATA, an
appropriate message will appear. Correct the
error and RUN again. Don't test the routine
until the "Position character ..." message
appears; the code is then ready for use. It's a
good idea to SAVE everything, just in case an
error has slipped past the 'check' in the
loader.
On a 16K Spectrum (assuming you have no other
machine code in memory) you might put the code
at address 31670. Type CLEAR 31669 and then
specify a "Load address" of 31670. Of course,
you can load the code anywhere you like,
although it's best to protect it with the CLEAR
command, or it could be overwritten by Basic.
The routine provides a Hi-res version of the
command:
!0.......^.........^.........^..
PRINT INK 8; PAPER 8; OVER 1;
AT y,x;CHR$ c
!1.......^.........^.........^.........^........
The syntax is rather different:
!0.......^.........^.........^..
RANDOMIZE x AND y=c+USR a
!1.......^.........^.........^.........^........
Where 'x' and 'y' are the horizontal and vert-
ical coordinates of the top left corner of the
character, 'c' is the ASCII code of the char-
acter and 'a' is the address where you stored
the routine. So:
!0.......^.........^.........^..
RANDOMIZE 0 AND 175=65+USR
64500
!1.......^.........^.........^.........^........
will position a letter 'A' (character 65) at the
top left corner of the screen, assuming you
stored the machine code at address 645000. The
RANDOMIZE is a 'dummy' to hold the result of the
USR call. If your program uses random numbers
you should replace RANDOMIZE with a dummy
variable assignment, such as:
!0.......^.........^.........^..
LET dummy=0 AND 175=65+USR
64500
!1.......^.........^.........^.........^........
You're allowed to specify coordinates or char-
acter codes with expressions as well as vari-
ables or numbers. For instance:
!0.......^.........^.........^..
RANDOMIZE xpos+xdir AND ypos-
ydir=CODE "*"+USR move
!1.......^.........^.........^.........^........
is allowed. Our routine uses a neat technique to
fetch the three previous expressions on the
line, before the USR call. If there are more or
less than three values, a 'Parameter' error will
be reported. Make sure that you've used the
correct separators - AND, equals and plus -
between the coordinates, the character code and
the USR call. If you're using calculations more
complicated than addition, subtraction, multi-
plication and division, you may need to put each
coordinate or character code in brackets - so
that the routine can distinguish them.
The machine code contains extensive error
trapping. It won't let you use y coordinates
less than seven, since each character is eight
lines high. A character at coordinates 0,6 would
have its bottom line at y coordinate minus one!
The "Integer out of range" error message appears
if you use vertical coordinates less than seven
or greater than 175. X coordinates beyond 248
simply "wrap around" to the opposite side of the
screen; decimal values are rounded to the
nearest whole number.
!0.......^.........^.........^..
!B
A SMOOTH OPERATOR
!1.......^.........^.........^.........^........
The second Basic program shows the features of
the routine quite clearly. A ball bounces around
the screen at a variety of speeds. It's pos-
itioned using "LET d=" rather than "RANDOMIZE"
so that the random number sequence is not
constantly re-started whenever the ball moves.
The listing is fairly straightforward, but make
sure you type commas (not semi-colons) in line
330.
The machine code always uses the OVER 1
setting, so that any character can be erased
without destroying the background, simple by re-
drawing it in the same place. The characters
take on the colour of the INK where they are
plotted. This avoids the need for complicated
code to save and restore colours, and prevents
weird effects as objects pass one another.
The Spectrum normally uses character codes 165
to 255 to represent keywords - words like THEN,
PRINT and so on. There's never any need to zoom
those around the screen, so our machine code
program lets you define an extra 91 user-
defined graphics in their place. Together with
the 21 standard user-defined graphics, this
gives you 122 characters to play with.
!0.......^.........^.........^..
!B
--------------------------------
LABELS |ADDRESS |COMMENT
--------------------------------
| |SYSTEM POINTERS
--------------------------------
UDGS | 5C7B |User graphics
| |pointer
CHARS | 5C36 |Character set
| |pointer
BLKCH | 5C92 |Block graphic
| |buffer
STACK | 5C65 |Maths stack end
| |pointer
STBOT | 5C63 |Maths stack
| |start pointer
--------------------------------
| |ROM ROUTINES
--------------------------------
MAKEB | 0B38 |Make graphic
| |in B
PIXEL | 22AA |Find address of
| |pixel
POP_A | 2DA2 |Pop A from
| |maths stack
--------------------------------
!2.......^.........^.........^.........^.........^.........^....
The table above shows the system pointers and ROM routines used
in Smooth Move, giving their labels and addresses. This is for
the assembler's use only, and need not be typed in.
!1.......^.........^.........^.........^........
!B
In principle the new characters are defined in
exactly the same way as the old ones. They
follow the others in memory, which means that
you will have to expand the user-defined
graphics area before you can define more than
the standard 21 characters. On a 48K computer
you'd use the following commands to expand the
graphics area to cope with 122 characters:
!0.......^.........^.........^..
CLEAR 64559: POKE 23675,48:
POKE 23676,252
!1.......^.........^.........^.........^........
The POKEs adjust the system variable UDG so that
it points an extra 728 (91*8) bytes further down
memory. They don't reserve memory for any
machine code, so you'd probably use CLEAR 64499
and load the Smooth Move code at 64500 (or
thereabouts).
On a 16K computer load the machine code at
31670 and reserve space with:
!0.......^.........^.........^..
CLEAR 31669: POKE 23675,48:
POKE 23676,124
!1.......^.........^.........^.........^........
When you come to program the user-defined
graphics, you POKE the patterns into memory as
usual. The only difference is that you can't use
the USR "letter" function to locate characters
after USR "u". The extra characters still follow
at eight-byte intervals. User-defined graphic
"a" has character code 144, so you can find the
definition of the character with code 'n' by
typing:
!0.......^.........^.........^..
PRINT USR "a"+8*(n-144)
!B
TRICKS OF THE TRADE
!1.......^.........^.........^.........^........
Smooth Move uses some interesting machine code
tricks, so I've listed the assembler code of the
program as well as the Basic loader. This is the
longest listing, assembled using version 2.1 of
Picturesque's excellent EDITAS assembler.
!0.......^.........^.........^..
100 REM SMOOTH MOVE DEMO
110 REM By Simon N Goodwin
120 REM
130 REM Load code
140 CLEAR 30999
150 LOAD "Mover"CODE 31000
160 REM Set area
170 LET xmax=247
180 LET ymax=168
190 REM Set up positions
200 LET xpos=INT (RND*200)
210 LET ypos=17
220 LET xdir=INT (RND*6+1)
230 LET ydir=-INT (RND*5+1)
240 REM Define ball
250 RESTORE
260 FOR i=USR "a" TO USR "e"
270 READ d
280 POKE i,d
290 NEXT i
300 LET shape=144
310 INK 6: PAPER 2: BORDER 5
320 FOR i=0 TO 21
330 PRINT AT i,0, INVERSE 1,
350 NEXT i
360 GO TO 480
390 REM Move ball
400 LET oldx=xpos
410 LET oldy=ypos
420 LET xpos=xpos+xdir
425 IF xpos>1 THEN IF xpos<xma
x THEN GO TO 440
430 LET xpos=oldx: LET xdir=INT
(RND*7+1)*-SGN xdir
435 BEEP .03,15
440 LET ypos=ypos+ydir
445 IF ypos>7 THEN IF ypos<yma
x THEN GO TO 460
450 LET ypos=oldy: LET ydir=INT
(RND*5+1)*-SGN ydir
455 BEEP .03,30
460 LET d=oldx AND oldy=shape+U
SR 31000
470 LET shape=shape+1-4*(shape=
147)
480 LET d=xpos AND ypos=shape+U
SR 31000
490 GO TO 400
590 REM Ball definition
600 DATA 60,66,135,143,143,159,
126,60
610 DATA 60,66,193,225,249,253,
126,60
620 DATA 60,126,249,241,241,225
,66,60
630 DATA 60,126,191,159,135,131
,66,60
640 DATA 0
!2.......^.........^.........^.........^.........^.........^....
A demonstration program featuring a ball bouncing around the
screen at a variety of speeds. (Make sure you type commas and
not semi-colons in line 330.)
!1.......^.........^.........^.........^........
!B
The assembler listing starts with the defin-
ition of a few constants used later in the
program. These are defined at the head of the
listing so that they can be checked and altered
easily. They also make the program easier to
read, especially if it doesn't immediately
spring to mind that, say, 5C65H is the address
of the Maths stack pointer! Three ROM routines
have been used, to minimise the program size and
keep things simple.
The machine code is in three main sections.
First, the parameters (the coordinates and char-
acter number) are checked, then the character
definition is located, and finally the character
is positioned on the screen. The parameter
checking relies on the way the Spectrum eval-
uates expressions. In a simple sum like:
!0.......^.........^.........^..
PRINT 2+3*4
!1.......^.........^.........^.........^........
the computer must work out the multiplication
before it does the addition. This is because the
correct answer is 14 (or 2+12), not 20 (5*4).
The Smooth Move code takes advantage of the
way Basic works out the result of a calculation,
by ensuring that the coordinates and character
number have been worked out, but not combined
together, as the USR call is performed. The
three numbers are languishing on a "maths stack"
of temporary results during the USR call. The
machine code can read the numbers which have
been so helpfully made ready by Basic, and then
put things tidy afterwards so that Basic can
carry on once the USR call is over.
!0.......^.........^.........^..
100 REM SMOOTH MOVE LOADER
110 REM By Simon N Goodwin
120 REM
200 INPUT "Load address";l
210 LET c=0
220 FOR i=l TO l+119
230 READ d
240 LET c=c+d
250 POKE i,d
260 NEXT i
270 IF c<>13017 THEN PRINT "Er
ror in DATA": STOP
280 PRINT "Position character c
AT x,y with"
290 PRINT "RANDOMIZE x AND y =
c +USR ";l
300 PRINT '"Save everything, ju
st in case..."
310 SAVE "SMOOTH/BAS"
320 SAVE "SMOOTH/COD"CODE l,120
330 STOP
400 DATA 42,101,92,229,235,42
410 DATA 99,92,1,15,0,9
420 DATA 237,82,40,2,207,25
430 DATA 205,162,45,254,128,56
440 DATA 11,71,214,144,56,19
450 DATA 237,91,123,92,24,4
460 DATA 237,91,54,92,38,0
470 DATA 111,41,41,41,25,24
480 DATA 6,205,56,11,33,146
490 DATA 92,229,221,225,205,162
500 DATA 45,103,229,205,162,45
510 DATA 225,111,229,14,8,225
520 DATA 37,229,36,197,68,77
530 DATA 205,170,34,193,71,175
540 DATA 176,221,126,0,40,17
550 DATA 235,38,0,111,62,8
560 DATA 144,71,41,16,253,235
570 DATA 126,170,119,35,123,174
580 DATA 119,221,35,13,32,213
590 DATA 225,225,34,101,92,201
!2.......^.........^.........^.........^.........^.........^....
If you've not yet got hold of an assembler, here's a Basic
loader program allowing you to load the Smooth Move.
!1.......^.........^.........^.........^........
!B
This gives us a convenient way of passing
numbers from Basic to machine code, without the
hassle of PEEKing and POKEing. We can ensure
that temporary results are ready by our choice
of separators between the coordinates. In the
command:
!0.......^.........^.........^..
RANDOMIZE x AND y=c+USR a
!1.......^.........^.........^.........^........
Basic must do the addition first, to get the
correct answer - adding is always done before
comparison (=) and comparisons are done before
AND. This idea is explained on page 12 of the
thin "Introduction" manual which came with your
Speccy.
On the way through the expression, Basic works
out any calculations needed to find x, y and c,
since those calculations should have a higher
priority than the 'plus' at the end of the line.
If you want to use equals, AND, or other so-
called "logical operations" in your calculation
of x, y and c, you must put the relevant
calculations in brackets, so that Basic will
work out the whole value before it reaches the
USR call. A list of priorities is on page 201 of
the Spectrum manual. Logical operations have
priority '2', '3', '4' or '5'.
Basic puts temporary results in an area called
the "maths stack". Two system variables are used
to mark the top and bottom of this area; each
value stored within it takes up five bytes.
Lines 1340-1450 of the assembler are used to
check that the maths stack is 15 bytes long when
the USR call is reached; this means three values
are ready. If the stack does not contain three
values, the routine stops with a 'Parameter'
error - this is generated by lines 1440 & 1450.
!0.......^.........^.........^..
!B
A CHARACTER STUDY
!1.......^.........^.........^.........^........
The ROM subroutine POP_A is used to read a
number from the maths stack and into the A
register. Lines 1470-1560 fetch the character
code, which is the last thing calculated and
hence at the 'top' of the stack. They test the
code to decide whether the character is a block
graphic, a user-defined graphic or an ASCII
character. There's no definition of the block
graphics in the ROM - those are generated as
required by a routine called MAKEB, which puts
the pattern specified by a code in the B regis-
ter at address BLKCH.
In the case of ASCII or user-defined charac-
ters, the routine finds the appropriate start
address from the system variables (UDGS points
to the user-defined graphics and CHARS points to
the ASCII symbols). The character code is multi-
plied by eight (since each definition takes
eight bytes) and the location of the character
is found by adding the start address to the
resultant value. GFONT copies the address of the
character definition into register IX, for safe-
keeping.
POP_A is used twice more to fetch the y and x
coordinates where the character is to be
displayed. The loop from PLINE onwards puts the
character into video memory, one line at a time.
Register C is used to count the lines.
The program takes the coordinates of each line
and uses a ROM call to find the address where
that line should appear. The ROM subroutine
named PIXEL takes x and y coordinates in regis-
ters C and B, returning with the address of the
byte required in HL and the position within the
byte in A. The character definition is fetched
and shifted sideways if need be.
Each line of the Spectrum display corresponds
to 32 bytes of video memory. The contents of
that memory determines what's displayed on the
line. The normal Spectrum PRINT routine uses one
byte per character on each line, which means
that characters cannot be printed partly in one
byte and partly in the next. Smooth Move allows
you to split characters between one byte and the
next (hence the finer control over positioning).
The character code is put into one end of the HL
register pair, which is shifted sideways until
it's at the required place on the boundary
between H and L. The ADD Hl,HL instruction is
used to shift the value - every time you add a
binary value to itself it moves one place to the
left, because each column has twice the value of
the one to its right.
At STORE the graphic line is mixed into the
display with the XOR instruction - the machine
code equivalent of PRINT OVER. The program loops
back to PLINE until all eight lines of the
character have been positioned.
!0.......^.........^.........^..
!B
TIDYING UP
!1.......^.........^.........^.........^........
The routine can't return to Basic until both
stacks - the processor stack and the maths stack
- have been put back the way they were found.
Line 2330 throws away the coordinate information
which was on the processor stack. Finally, the
value of the maths stack pointer is retrieved,
so that Basic doesn't get confused by the sudden
loss of three data items. The USR function
returns the value zero, since B and C have both
counted down to nothing.
This program is only an introduction to Hi-res
animation on the Spectrum. The best graphic
routines handle large shapes, with automatic
animation and motion, collision detection, and
so forth. One day I might divulge the secrets of
the YS Sprite System, which puts most of the
power of a dedicated arcade machine at your
fingertips - that's if I ever finish writing it
...!
!2.......^.........^.........^.........^.........^.........^....
!B
The assembler code for Smooth Move - just so that you can see
the interesting machine code tricks that Simon's used.
FDE8 1320 ORG 65000
1330 ; "Fetch end of stack"
FDE8 2A655C 1340 MOVER LD HL,(STACK)
FDEB E5 1350 PUSH HL
1360 ; "3 numbers on stack?"
FDEC EB 1370 EX DE,HL
FDED 2A635C 1380 LD HL,(STBOT)
FDF0 010F00 1390 LD BC,15
FDF3 09 1400 ADD HL,BC
FDF4 ED52 1410 SBC HL,DE
FDF6 2802 1420 JR Z,FCODE
1430 ; "3 parameters needed!"
FDF8 CF 1440 RST #08
FDF9 19 1450 DEFB 25
1460 ; "Find the char. code"
FDFA CDA22D 1470 FCODE CALL POP_A
1480 ; "Divide into 3 groups:"
1490 ; " 0-127 ASCII chars"
1500 ; "128-143 block graphics"
1510 ; "144-255 user defined"
FDFD FE80 1520 CP 128
FDFF 380B 1530 JR C,ASCII
FE01 47 1540 LD B,A
FE02 D690 1550 SUB 144
FE04 3813 1560 JR C,BLOCK
1570 ; "Must be a UDG"
FE06 ED5B7B5C 1580 LD DE,(UDGS)
FE0A 1804 1590 JR INDEX
1600 ;
FE0C ED5B365C 1610 ASCII LD DE,(CHARS)
1620 ; "Find character form"
FE10 2600 1630 INDEX LD H,#00
FE12 6F 1640 LD L,A
FE13 29 1650 ADD HL,HL
FE14 29 1660 ADD HL,HL
FE15 29 1670 ADD HL,HL
FE16 19 1680 ADD HL,DE
FE17 1806 1690 JR GFONT
1700 ;
FE19 CD380B 1710 BLOCK CALL MAKEB
FE1C 21925C 1720 LD HL,BLKCH
FE1F E5 1730 GFONT PUSH HL
FE20 DDE1 1740 POP IX
1750 ; "Fetch Y coordinate"
FE22 CDA22D 1760 CALL POP_A
FE25 67 1770 LD H,A
1780 ; "Fetch X coordinate"
FE26 E5 1790 PUSH HL
FE27 CDA22D 1800 CALL POP_A
FE2A E1 1810 POP HL
FE2B 6F 1820 LD L,A
FE2C E5 1830 PUSH HL
1840 ; "Process 8 screen lines"
FE2D 0E08 1850 LD C,8
FE2F E1 1860 PLINE POP HL
1870 ; "Step up to next line"
FE30 25 1880 DEC H
FE31 E5 1890 PUSH HL
FE32 24 1900 INC H
1910 ; "Convert coord in H,L"
1920 ; "to address in HL & A"
FE33 C5 1930 PUSH BC
FE34 44 1940 LD B,H
FE35 4D 1950 LD C,L
FE36 CDAA22 1960 CALL PIXEL
FE39 C1 1970 POP BC
1980 ; "Copy bit offset to B"
FE3A 47 1990 LD B,A
2000 ; "See if char is on grid"
FE3B AF 2010 XOR A
FE3C B0 2020 OR B
2030 ; "Read font anyway"
FE3D DD7E00 2040 LD A,(IX+0)
2050 ; "Store NOW if on grid"
FE40 2811 2060 JR Z,STORE
2070 ; "Generate 16 bit mask"
FE42 EB 2080 EX DE,HL
FE43 2600 2090 LD H,0
FE45 6F 2100 LD L,A
2110 ; "Reverse shift count"
FE46 3E08 2120 LD A,8
FE48 90 2130 SUB B
FE49 47 2140 LD B,A
FE4A 29 2150 SHIFT ADD HL,HL
FE4B 10FD 2160 DJNZ SHIFT
2170 ; "Put mask in DE"
FE4D EB 2180 EX DE,HL
2190 ; "Mix into display"
FE4E 7E 2200 LD A,(HL)
FE4F AA 2210 XOR D
FE50 77 2220 LD (HL),A
FE51 23 2230 INC HL
FE52 7B 2240 LD A,E
FE53 AE 2250 STORE XOR (HL)
FE54 77 2260 LD (HL),A
2270 ; "Advance through font"
FE55 DD23 2280 INC IX
2290 ; "Count one line done"
FE57 0D 2300 DEC C
FE58 20D5 2310 JR NZ,PLINE
2320 ; "Tidy stacks; n.b. BC=0"
FE5A E1 2330 POP HL
FE5B E1 2340 POP HL
FE5C 22655C 2350 LD (STACK),HL
FE5F C9 2360 RET
2370 END
!1.......^.........^.........^.........^........
!B
--
from Your Spectrum #7 (Sep.1984)
--
!$