!0.......^.........^.........^..
!B
\H11\H07\H10\H00          S P R I T E           
\H11\H07\H10\H00        D E S I G N E R         

!2.......^.........^.........^.........^.........^.........^....
 User-defined sprites on the Spectrum? It's not that difficult!
 Get things moving on-screen with more machine code magic from
                      Toni Baker.
!1.......^.........^.........^.........^........
This  month's  block of crafty code makes  weird
shapes fly around all over the screen. In  fact,
you shouldn't find it too hard to add a few bits
and pieces to the idea yourself. It's all basic-
ally   very   simple.  You  design  up  to  five
different sprites - of any figure, shape, design
and  so  on  - which in this case are 16  pixels
wide  by  16  pixels  high;  imagine four  user-
defined graphics characters glued together in  a
square and you have the idea. But - and this  is
where the story really begins - there's more  to
this program than meets the eye, for it  incorp-
orates a rather mind-blowing idea ...

!0.......^.........^.........^..
!B
MOTION PICTURES
!1.......^.........^.........^.........^........
You  see, once you've defined a sprite, you  can
then  specify its coordinates on the screen  and
its velocity across the screen. In other  words,
they  move!  Curiouser  and  curiouser.  Once  a
sprite  is set in motion, you can continue  exe-
cuting  more Basic or machine code. The  sprites
will keep moving on-screen, simultaneously  with
any other program.
  One line of Basic can set a sprite in  motion.
The next Basic line will, of course, be executed
in  sequence - but it'll be executed whilst  the
sprite is moving. Thus, the setting in motion in
the first place is all you have to worry  about.
How  it  all  works is quite intricate, so  I'll
explain  in  a  moment  or two; if you find  the
blurb  a  bit  too  heavy going, try quaffing  a
cuppa or two before attempting to follow it.  In
the meantime, I'll tell you how to integrate  my
machine code with your Basic.

!0.......^.........^.........^..
!B
DOWN TO DESIGN
!1.......^.........^.........^.........^........
The first statement of your Basic program must
be  a  "DIM  s$(x,8)";  where  x is the  maximum
number  of  things  you  want flying around  on-
screen  at  any  one time. Then, you'll want  to
include  the statements "LET on=33013" and  "LET
off=33020". From here on in it's up to you.  The
statement  "RANDOMIZE  USR  on"  will bring  the
moving sprite facility into action, whereas  the
statement  "RANDOMIZE USR off"will bring  things
back to normal.
  The  sprites themselves are defined using  the
ordinary user-defined graphics. You can have  up
to five different designs on-screen at once,
which are:
!0.......^.........^.........^..
Sprite 1=UDGs A,B,C,D
Sprite 2=UDGs E,F,G,H
Sprite 3=UDGs I,J,K,L
Sprite 4=UDGs M,N,O,P
Sprite 5=UDGs Q,R,S,T
!1.......^.........^.........^.........^........
You can define them yourself in the usual ways.
  The  array,  s$, is the one that contains  all
the information, however, and each element  must
be  precisely defined. Now ... it's "pay  atten-
tion"  time.  Take a look at the box giving  the
explanation of the sprite parameters. (Note that
in the explanations given, I've used the  letter
'N'  to represent one of the strings in s$;  the
number is completely arbitrary.)
  The  element s$(N,8) is actually very  import-
ant. If it contains any character whose code  is
less  than  or  equal to 64 (decimal), then  the
sprite  is said to be inactive - that is,  it'll
not  appear on the screen. You can make as  many
alterations as you like to the other elements of
s$. Once all the alterations have been made, you
can  then  alter s$(N,8) and the sprite will  be
active and will start moving across the screen.
  If such a moving sprite collides with anything
on-screen,  or  if  it  hits  the  edges of  the
screen, then it'll instantly stop
!0.......^.........^.........^..
!B
--------------------------------
\H11\H07\H10\H00     THE SPRITE PARAMETERS      
--------------------------------
STRING  | EXPLANATION
ELEMENT |
--------------------------------
s$(N,1) |CHR$ (the sprite number
        | - between one & five)
--------------------------------
s$(N,2) |This must always be
        |initialised to CHR$(1)
--------------------------------
s$(N,3) |CHR$ (the number of
        |frames between 
        |successive movements)
--------------------------------
s$(N,4) |CHR$ (the figure's Y
        |coordinate)
--------------------------------
s$(N,5) |CHR$ (the figure's X
        |coordinate)
--------------------------------
s$(N,6) |CHR$ (the figure's
        |vertical displacement
        |each time it moves)
--------------------------------
s$(N,7) |CHR$( the figure's
        |horizontal displacement
        |each time it moves)
--------------------------------
s$(N,8) |This must be set last
        |of all, and must be any
        |character whose code is
        |greater than 64
--------------------------------

!2.......^.........^.........^.........^.........^.........^....
In the table above, it must be noted that 'N' is used to repre-
sent one of the strings in s$ - the number is arbitrary.
!1.......^.........^.........^.........^........
!B
moving  and  deactivate.  Element  s$(N,8)  will
automatically reset to CHR$(0). You can test for
this occurrence in a Basic program.

!0.......^.........^.........^..
A TIMELY INTERRUPTION
!1.......^.........^.........^.........^........
OK,  it's  tea-break  time! Arm yourself with  a
cuppa and I'll explain how it all works.
  The machine code is an interrupt routine. This
means that the program runs itself automatically
50  times a second. Each time it runs it  checks
out the array s$ and shuffles sprites around the
screen   accordingly.   The   basic  instruction
"RANDOMIZE   USR   on"   simply  activates  this
interrupt procedure, whereas "RANDOMIZE USR off"
deactivates  it. Interrupt procedures are  quite
clever;  however, it's very easy to muck  things
up  and just a tiny little bug will spell  total
disaster   and   blast   the  poor  Speccy  into
oblivion.   This  is  true  simple  because  the
program runs itself once for every new TV frame,
whether you want it to or not.
  Take a peek at the basic program that's  lying
around in this article. It manages to do in just
a few statements what would otherwise have  been
quite  a  complicated  program;  it  produces  a
figure  which  bounces  around  the screen.  The
first seven lines just initialise the array, s$,
and line 80 starts things moving. Line 90 is the
one  to think about - it looks like an  infinite
loop,  but  in fact it's not; it's just  waiting
until the figure hits an edge. If you break  out
of  the  program at any time you'll notice  that
the figure will keep moving even while you  type
a  command,  until it hits a wall (when it  will
stop). At this point, you should type "RANDOMIZE
USR off" before you do anything else.
  Well, that's it for this issue. I'm just going
off to stick my head in a bucket of  inspiration
-  hopefully in time for me to produce yet  more
gems next month. See you then.

!2.......^.........^.........^.........^.........^.........^....
!B
--------------------------------.-------------------------------
  10 DIM s$(1,8)                Dimensions the array, s$
--------------------------------.-------------------------------
  20 LET on=33013               Initialise the variables
  30 LET off=33020
--------------------------------.-------------------------------
  40 FOR j=1 TO 8               Read the data in line 160
  50 READ a
  60 LET s$(1,j)=CHR$ a
  70 NEXT j
--------------------------------.-------------------------------
  80 RANDOMIZE USR on            Switches on the sprite facility
--------------------------------.-------------------------------
  90 IF s$(1,8)="A" THEN  GO TO  Waits until a sprite hits the
90                               edge of the screen
--------------------------------.-------------------------------
 100 LET y=CODE s$(1,4)          'y' is the Y coordinate of the
                                 sprite
--------------------------------.-------------------------------
 110 LET x=CODE s$(1,5)          'x' is the X coordinate of the
                                 sprite
--------------------------------.-------------------------------
 120 IF y=0 OR y=22 THEN  LET s$ Reverses the Y movement of
(1,6)=CHR$ (256-CODE s$(1,6)): B the sprite if necessary
EEP .03,24
--------------------------------.-------------------------------
 130 IF x=0 OR x=30 THEN  LET s$ Reverses the X movement of
(1,7)=CHR$ (256-CODE s$(1,7)): B the sprite if necessary
EEP .03,12
--------------------------------.-------------------------------
 140 LET s$(1,8)="A"             Reactivates the sprite
--------------------------------.-------------------------------
 150 GO TO 90                    A loop to send the action back
                                 to line 90.
--------------------------------.-------------------------------
 160 DATA 1,1,2,10,5,255,1,65    Contains the data for s$
--------------------------------.-------------------------------

The Basic program to get things moving on-screen - type it in
and see ...

!B
Machine code       Assembler          Comments
---------.---------.------------------.-------------------------
                   ORG  80F5
---------.---------.------------------.-------------------------
3E80     ON        LD   A,#80
ED47               LD   I,A           I:=80
ED5E               IM   2             Activate interrupt routine
C9                 RET
---------.---------.------------------.-------------------------
ED46     OFF       IM   0             Deactivate routine
C9                 RET
---------.---------.------------------.-------------------------
0181     IADDR     DEFW 8101          Direct interrupt control
                                      to address 8101
---------.---------.------------------.-------------------------
F5       SPRITES   PUSH AF
C5                 PUSH BC
D5                 PUSH DE
E5                 PUSH HL
DDE5               PUSH IX            Stack all registers used
                                      by the routine
DD2A4B5C           LD   IX,(VARS)     Point IX to array s$
DD7E00             LD   A,(IX+#00)
FED3               CP   #D3
200F               JR   NZ,EXIT       Jump if first variable is
                                      not array s$
DD4604             LD   B,(IX+#04)    B:=1st dimension of array
                                      (number of sprites)
---------.---------.------------------.-------------------------
C5       SP_LOOP   PUSH BC            Stack this number
010800             LD   BC,#0008
DD09               ADD  IX,BC         Point IX to start of next
                                      sprite(next array element)
CD7181             CALL NXT_SPRT      Treat next sprite
C1                 POP  BC            B:=remaining number of
                                      sprites to treat
10F4               DJNZ SP_LOOP
---------.---------.------------------.-------------------------
DDE1     EXIT      POP  IX
E1                 POP  HL
D1                 POP  DE
C1                 POP  BC
F1                 POP  AF            Restore all registers
FF                 RST  #38           Carry out normal 
                                      interrupt procedures
C9                 RET
---------.---------.------------------.-------------------------
CB0C     LINE      RRC  H
CB0C               RRC  H
CB0C               RRC  H             HL:=coded print position
011F00             LD   BC,#001F
09                 ADD  HL,BC         Move print position one
                                      square down and one left
CB04               RLC  H
CB04               RLC  H
CB04               RLC  H             HL:=correct print position
C9                 RET
---------.---------.------------------.-------------------------
CD4081   WIPE      CALL H_WIPE        Erase top half of sprite
CD2981             CALL LINE          Point HL to bottom half of
                                      sprite
---------.---------.------------------.-------------------------
CD4481   H_WIPE    CALL Q_WIPE        Erase one square of sprite
23                 INC  HL            Point HL to remaining square
---------.---------.------------------.-------------------------
E5       Q_WIPE    PUSH HL
0608               LD   B,#08         B:=Number of rows per 
                                      square
---------.---------.------------------.-------------------------
3600     WP_LOOP   LD   (HL),#00      Erase next row
24                 INC  H             Point HL to next row
10FB               DJNZ WP_LOOP
E1                 POP  HL
C9                 RET
---------.---------.------------------.-------------------------
AF       TEST      XOR  A             A:=00
CD5581             CALL H_TEST        Test upper half of sprite
                                      position
CD2981             CALL LINE          Point HL to lower half
---------.---------.------------------.-------------------------
CD5981   H_TEST    CALL Q_TEST        Test one square of sprite
                                      position
23                 INC  HL            Point HL to remaining square
---------.---------.------------------.-------------------------
E5       Q_TEST    PUSH HL
0608               LD   B,#08
---------.---------.------------------.-------------------------
B6       TS_LOOP   OR   (HL)          If any pixel is set then A
                                      becomes non-zero
24                 INC  H             HL points to next row
10FC               DJNZ TS_LOOP
E1                 POP  HL
C9                 RET
---------.---------.------------------.-------------------------
78       FIND_ADDR LD   A,B           This subroutine computes
E618               AND  #18           in HL the print pos of the
F640               OR   #40           square (on-screen) which
67                 LD   H,A           has PRINT-AT coords given
78                 LD   A,B           by registers B,C
0F                 RRCA
0F                 RRCA
0F                 RRCA
E6E0               AND  #E0
B1                 OR   C
6F                 LD   L,A
C9                 RET
---------.---------.------------------.-------------------------
DD7E07   NXT_SPRT  LD   A,(IX+#07)    A:=activation flag
FE40               CP   #40
D8                 RET  C             Return if sprite inactive
DD3501             DEC  (IX+#01)      Count frames between
                                      movements
C0                 RET  NZ            Return if the sprite does
                                      not require moving
DD7E02             LD   A,(IX+#02)    A:=frame interval between
                                      movements
DD7701             LD   (IX+#01),A    Re-initialise frame count
DD4603             LD   B,(IX+#03)    B:=y coordinate
DD4E04             LD   C,(IX+#04)    C:=x coordinate
CD6281             CALL FIND_ADDR     HL:=print pos of sprite
CD3A81             CALL WIPE          Erase sprite
DD7E03             LD   A,(IX+#03)    A:=y coordinate
DD8605             ADD  A,(IX+#05)    A:=intended y coordinate
FE17               CP   #17
301F               JR   NC,NS_EXIT    Jump if intended y coord
                                      out of range
47                 LD   B,A           B:=intended y coordinate
DD7E04             LD   A,(IX+#04)    A:=x coordinate
DD8606             ADD  A,(IX+#06)    A:=intended x coordinate
FE1F               CP   #1F
3014               JR   NC,NS_EXIT    Jump if intended x coord
                                      out of range
4F                 LD   C,A           C:=intended x coordinate
---------.---------.------------------.-------------------------
C5                 PUSH BC
CD6281             CALL FIND_ADDR     HL:=intended print pos
CD4E81             CALL TEST          Test for collision
C1                 POP  BC            BC:=intended coordinates
A7                 AND  A
2008               JR   NZ,NS_EXIT    Jump if sprite has hit
                                      something
DD7003             LD   (IX+#03),B    Store new y coordinate
DD7104             LD   (IX+#04),C    Store new x coordinate
180A               JR   NS_DRAW
---------.---------.------------------.-------------------------
DD360700 NS_EXIT   LD   (IX+#07),#00  Deactivate sprite
DD4603             LD   B,(IX+#03)    B:=old y coordinate
DD4E04             LD   C,(IX+#04)    C:=old x coordinate
---------.---------.------------------.-------------------------
DD6E00   NS_DRAW   LD   L,(IX+#00)    L:=sprite type number
2D                 DEC  L             L now in range 0 to 4
2600               LD   H,#00         HL now in range 0 to 4
29                 ADD  HL,HL
29                 ADD  HL,HL
29                 ADD  HL,HL
29                 ADD  HL,HL
29                 ADD  HL,HL         Multiply by 32d
ED5B7B5C           LD   DE,(UDG)      Point DE to graphic A
19                 ADD  HL,DE         Point HL to required
                                      sprite graphics
E5                 PUSH HL
CD6281             CALL FIND_ADDR     HL:=print pos of sprite
D1                 POP  DE            DE:=address of pixel
                                      information
---------.---------.------------------.-------------------------
CDDB81   DRAW      CALL H_DRAW        Draw upper half of sprite
CD2981             CALL LINE          Point HL to lower half
---------.---------.------------------.-------------------------
CDDF81   H_DRAW    CALL Q_DRAW        Draw next square of sprite
23                 INC  HL            Point HL to next square
---------.---------.------------------.-------------------------
E5       Q_DRAW    PUSH HL
0608               LD   B,#08
---------.---------.------------------.-------------------------
1A       DR_LOOP   LD   A,(DE)
77                 LD   (HL),A        Print next row
13                 INC  DE
24                 INC  H             Point HL to next row
10FA               DJNZ DR_LOOP
E1                 POP  HL
C9                 RET
---------.---------.------------------.-------------------------
!1.......^.........^.........^.........^........

!B
--
from Your Spectrum #10 (Dec/Jan.1984/85)
--
!$
