!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)
--
!$
