Issue 82 Issue 83 Contents Issue 84

Steve Marsden originally worked fur Hewson in 1984, where he produced Technician Ted. He then moved to BT, where he produced Costa Capers in mid 1985. City Slicker came next in 1986 for Hewson.

In early 1987 Steve moved to Gremlin Graphics and produced Final Matrix, the 1997 Christmas compendium. April 1988 saw the release of Blood Brothers for Gremlin. More recently, Steve finished 4x4 Off-road Racing for US Gold.

During the four years of programming, Steve has worked with Dave Cooke. Before he started programming, he made silicon chips. Steve Marsden is a pilot.

...DOES SPRITE DETECTION WORK?

In a program such as a game or a utility with a moving pointer driven by a mouse/tracker ball, there will be some form of detection routine if the moving objects interact in some way. If the detection routine is a good one, then the results on the screen will be visually more pleasing. We've all played games where you are frantically trying to move your main character away from a hostile alien, only to be killed off by another which looks to be a good two inches from the main character.

It can be extremely difficult to come up with an algorithm which doesn't take up hours of execution time and still leave an acceptable collision detection system. At the end of the day therefore it is all down to how much - or how little - time the programmer can allow for the detection routine. At the end of this article, I have written a small routine which gives the ultimate in detection - pixel detection. Like your computer teachers always say - "A computer can do anything you program it to do!" This is also the case when designing detection routines. Most programmers go for the first method I will describe - Box detection.

Virtually every sprite that has eve been drawn, starts out life as a rectangular array of bytes. The array of bytes is then transformed into a super duper spacecraft or other graphic with a suitable editing package and the resulting data is called a sprite frame. To move this sprite around the screen, the animation routine will simply output the rectangular array to various positions on the screen map. Now unless we draw a rectangular box shape which perfectly lines up with the edges of the array of bytes making up the sprite, then the odd corner of the array will consist of fresh air or nothingness (Fig 1).

However, we don't usually set up a rectangular array of say 10 characters by 10 characters in size and only draw a 2 x 2 character shape. Moreover we select a suitable size of rectangle so that we more or less fill the rectangle with only a few bare corners showing. This factor allows us to use a mathematical form of detection known as box detection.

Normally, a sprite will have a few important variables associated with it. These are at the very least an X co-ordinate, Y co-ordinate and possibly variables telling us how wide the sprite is and also how it is. Now it is quite an easy matter from this information about a sprite to determine whether an overlap has occurred between two sprite rectangles with a little elementary maths.

if all law sprites were boxes or rectangles, then this method would be infallible. The truth is, however, that our sprites have corners missing as in Fig. 1. Suppose two sprites are overlapping by their corners, the routine above will sense that they are overlapping - but on the screen we would not see an overlap the pixel meeting pixel sense. To us, it would seem that the two sprites were miles apart but they still detected each other. What is required then is a routine which actually determines pixel to pixel collisions.

The Hewson game Technician Ted used pixel detection and made it possible to use the feature extensively in its gameplay. The main character could literally walk up to a hostile sprite until his nose was only one pixel away from death. This sounds quite a complicated feat on the surface, but in reality it is so easy - the logical instruction 'AND' does it all for you.

Just think about it at the lowest level. A sprite is data and the screen is also data - just numbers. To draw a sprite onto a screen really means that we are taking one byte of data from one place and mixing it with another byte of data in another place and then storing the new data byte in the screen memory. Remember the truth table for the logical AND instruction:
ABC
000
010
100
111

Column A is one bit of data while column B is another bit of data. If we assume that a pixel is a '1' or a set bit in a byte then an overlap in logic terms means the case when A = B = l. The column C in this case produces a '1' when both A and B are '1'.

This shows clearly how we can use the AND to create a pixel detection system. The example program below uses an AND instruction in the detection routine but it does seem to be buried under a lot of other instructions - this is because a few other considerations have sometimes to be applied such as outputting the sprite to the screen. For the more technically minded out there, I have used and XOR type of output routine which combines the pixel detection as well.

A pixel detection system does not give any other information apart from telling us that an overlap has occurred. For example, a collision might occur between two sprites and a background feature such as a wall. The sprite to sprite collision might be acceptable - a friendly character perhaps - but the pixel detection cannot differentiate between pixels. A pixel could belong to a wall, a frog, a power pill, but the routine does not know where it originated from. This is why pixel detection can grow to very complicated lengths and as such it is why programmers usually shy away from it.

Preferably type in the source code into an assembler and produce the program that way. Fro those who haven't got an assembler (earn yourself a slapped wrist!), type in this BASIC program and input the decimal numbers from the listing supplied.

10 FOR N=32768 TO 33159
20 INPUT (N);"-";A
30 POKE N,A
40 PRINT N;" ";A
50 NEXT N

To run the program, do a RANDOMIZE USR 32768 and just watch what happens - hopefully a starry background with 6 square shapes moving around it. To return to BASIC press the SPACE bar. Poke the addresses 32792 and 32824 with any number from 1 to 6 to determine how many of the square shape you want.

Once the program has been entered, to save the machine code type in SAVE "detect" CODE 32768,391.

                                 ORG 08000H
NO_SHAPES:                       EQU 6
DETECT_DEMO:
        LD A,1                   ; set the border to blue
        OUT (£FE),A
        LD HL,£4000              ; clear the pixel screen
        LD DE,£4001
        LD BC,£17FF
        LD (HL),L
        LDIR
        CALL DRAW_STARS          ; draw in the background and borders
        LD IX,BLOB_VARS          ; set up IX to point to the first set
        LD B,NO_BLOBS            ; of shape variables and make B equal
                                 ; to the number of shapes we want
INIT_LOOP:
        PUSH BC                  ; preserve B - the counter
        LD D,(IX+1)              ; set up DE with the X and Y coords
        LD E,(IX+0)              ; of the shape
        CALL DRAW_BLOB           ; draw it to the screen
        LD C,4
        ADD IX,BC                ; make IX point to the next shape
        POP BC                   ; restore B
        DJNZ INIT_LOOP           ; loop back to draw in the rest of
                                 ; the shapes
MAIN_LOOP:                       ; synchronise the output loop with a
        LD BC,1900               ; HALT and set up BC for the flicker
DELAY:  DEC BC                   ; reducing counter
        BIT 7,B                  ; if BC has not gone negative then go
        JR Z,DELAY               ; back (1901 times)
        LD IX,BLOB_VARS          ; initialise IX to the shape variable
        LD B,NO_BLOBS            ; list and B as a counter to the
BLOB_LOOP:                       ; number of shapes required
        PUSH BC                  ; save the counter
        CALL OUTPUT_BLOB         ; CALL the main shape output and test
                                 ; routine
        CALL NC,CHANGE_DIR       ; NO CARRY from OUTPUT_BLOB
                                 ; means that collision has occurred so we
                                 ; CALL CHANGE_DIR to make the shape
        LD BC,4                  ; rebound
        ADD IX,BC                ; move IX on to the next set of shape
        POP BC                   ; variables and restore B
        DJNZ BLOB_LOOP           ; loop back as required
        LD A,£7F                 ; set A to the keyboard half row
        IN A,(£FE)               ; B-SPACE and read it
        RRA                      ; check Bit 0 (SPACE)
        JR C,MAIN_LOOP           ; jump back if not pressed
        RET                      ; else return to BASIC
CHANGE_DIR:
        CALL DIR10               ; CALL DIR10 to get a random value of
        LD (IX+2),A              ; 0,1,-1 into the accumulator. Store
        LD C,A                   ; it in the X movement variable
                                 ; (IX+2) and also temporarily in C
        CALL DIR10               ; do the same for the Y movement
        OR C                     ; variable and then OR it with C
                                 ; if the X and Y movement variables
        JR Z,CHANGE_DIR          ; are both zero then the shape would
                                 ; not move so jump back to CHANGE_DIR
        RET                      ; then return
DIR10:  CALL RAND_NUM            ; get an 8-bit random number
        CP 96
        JR NC,DIR20              ; jump if A>96
        LD A,-1                  ; if A is 0-95, then make A=-1
        RET
DIR20:  CP 160
        JR C,DIR30               ; jump if A<160
        LD A,1                   ; if A>160 then make A=1
        RET
DIR30:  XOR A                    ; if 96<=A<160 then make A=0
        RET
DRAW_STARS:
        LD BC,£FFBF              ; make BC = counter values
        LD DE,0                  ; DE = screen top left coordinates
ST10:   CALL PLOT                ; plot at DE
        INC E                    ; stEp along a pixel
        DJNZ ST10                ; and repeat for 255 times
        LD B,C                   ; make B = 191
ST20:   CALL PLOT                ; plot at top right corner
        INC D                    ; and step down the screen 191 times
        DJNZ ST20
        DEC B                    ; decrement B to make it = 255
ST30:   CALL PLOT                ; plot at bottom right corner
        DEC D                    ; step from right to left
        DJNZ ST30                ; for 255 times
        LD B,C                   ; make B = 191
ST40:   CALL PLOT                ; plot at bottom left corner
        DEC D                    ; and step up the left edge of the
        DJNZ ST40                ; screen for 191 times
        INC B                    ; make BC = 447
ST50:   PUSH BC                  ; save it
        CALL Y_RAND_NUM          ; get random number from 0-191
        LD D,A                   ; put it in D
        CALL RAND_NUM            ; now get one from 0-255
        LD E,A                   ; and put it in E
        CALL PLOT_ADDR           ; now plot at this random coordinate
        OR (HL)                  ; by OR'ing with the screen
        LD (HL),A                ; pointed at by HL
        POP BC                   ; restore the counter
        DEC BC                   ; decrement it and loop back for 448
        BIT 7,B                  ; times (until BC goes negative)
        JR Z,ST50
        RET
OUTPUT_BLOB:
        LD D,(IX+1)              ; set up DE from the shape coordinate
        LD E,(IX+0)              ; variables
        CALL DRAW_BLOB           ; rub out the blob from its present
                                 ; position
        LD A,(IX+2)              ; now add in the X movement variable
        ADD A,E                  ; to the X coordinate and put in E
        LD E,A
        LD A,(IX+3)              ; do the same with the Y movement
        ADD A,D                  ; variable but put it in D
        LD D,A
        CALL DRAW_BLOB           ; draw in the shape at the new moved
                                 ; position
        JR C,PUT_BACK            ; if there was a CARRY, then the new
                                 ; position has hit a pixel so jump
                                 ; to PUT_BACK and don't move
        LD (IX+1),D              ; if the new position was OK then
        LD (IX+0),E              ; store the new X and Y coordinates
        SCF                      ; and set the CARRY before returning
        RET                      ; to signal - MOVE SUCCESSFUL
PUT_BACK:
        CALL DRAW_BLOB           ; rub out the moved shape
        LD D,(IX+1)
        LD E,(IX+0)              ; Y position
        CALL DRAW_BLOB           ; and re-draw the shape at its first
        AND A                    ; position, CLEAR the carry flag
        RET                      ; before returning to signal - MOVE
                                 ; UNSUCCESSFUL
DRAW_BLOB:
        PUSH DE                  ; save DE
        CALL PLOT_ADDR           ; calculate the screen address at
                                 ; which to draw the shape (from DE)
        LD DE,SHAPE              ; make DE point to the shape data
        EX AF,AF'                ; clear the alternative carry flag
        AND A
        EX AF,AF'
        LD B,8                   ; there are 8 pixel rows in the shape
BLOB10: PUSH BC                  ; save the counter
        PUSH DE                  ; ... and the shape pointer
        LD B,C                   ; B = C = X pixel position (1 - 8)
        LD A,(DE)                ; get the shape data byte
        LD D,A                   ; put it in DE (E = 0)
        LD E,0
BLOB20: SRL D                    ; rotate DE as required to bring the
                                 ; shape data into the correct place
        DJNZ BLOB20              ; for outputting to the screen
                                 ; The detection and output stage
        LD A,D                   ; get the leftmost byte of data and
        LD (HL)                  ; logically XOR it with the screen
        LD (HL),A                ; data then store it in the screen
        AND D                    ; mask off the bits we have just put
        CP D                     ; in and check to see that they are
                                 ; the same - Zero flag set
        JR Z,BLOB30              ; jump if the same - no collision
        EX AF,AF'                ; set the alternative carry flag to
        SCF                      ; indicate that a collision has in
        EX AF,AF'                ; fact occurred
BLOB30: INC HL                   ; step on the screen point across
        LD A,E                   ; the screen and now treat the right
        XOR (HL)                 ; most byte of shape data in the same
        LD (HL),A                ; way - XORing to the screen, masking
        AND E                    ; the bits we are interested in and
        CP E                     ; comparing to check that they are
        JR Z,BLOB40              ; the same - jumping if they are
        EX AF,AF'                ; as before, set the alternative
        SCF                      ; carry flag if a collision has
        EX AF,AF'                ; occurred
BLOB40: DEC HL                   ; step the screen pointer back to its
        INC H                    ; first place and increment it down
        LD A,H                   ; the screen
        AND 7                    ; if within the same character cell
        JR NZ,BLOB50             ; then jump
        LD A,L                   ; else add 32 to the low byte of the
        ADD A,£20                ; screen pointer
        LD L,A
        JR C,BLOB50              ; if transition across a screen
                                 ; 'third' has been made then jump
        LD A,H                   ; else subtract 8 off the pointer
        SUB 8                    ; high byte
        LD H,A
BLOB50: POP DE                   ; restore the shape pointer
        POP BC                   ; and the 8 pixel row counter
        INC DE                   ; increment the shape pointer
        DJNZ BLOB10              ; and decrement the counter
        EX AF,AF'                ; make the alternative carry flag
        POP DE                   ; available to the CALLing routine
        RET                      ; restore DE and return
PLOT:
        PUSH BC                  ; save registers from corruption
        PUSH DE
        CALL PLOT_ADDR           ; calculate the screen plot address
        OR (HL)                  ; and OR in the pixel to
        LD (HL),A                ; the screen
        POP DE
        POP BC                   ; restore registers and return
        RET
PLOT_ADDR:
        LD A,D                   ; check the Y coordinate range to see
        CP 192                   ; that it is not off the screen
        RET NC                   ; return if it is
        AND £C0                  ; put the screen 'third' bits into
        RRA                      ; bits 3 and 4 with a 010 in bit
        SCF                      ; positions 5, 6 and 7
        RRA
        RRA
        XOR D                    ; merge in the bits 0, 1, and 2 from
        AND £F8                  ; the Y coordinate
        XOR D
        LD H,A                   ; and store in H
        LD A,E                   ; move the top 5 bits of the X co-
        RLCA                     ; ordinate into bits 0, 1, 2 and 6, 7
        RLCA
        RLCA
        XOR D                    ; merge in bits 3, 4 and 5 from the
        AND £C7                  ; Y coordinate
        XOR D
        RLCA                     ; rotate the byte twice more and
        RLCA                     ; hey presto we have
        LD L,A                   ; the low byte of the screen address
        LD A,E                   ; get the three lower bits of the X
        AND 7                    ; coordinate and increment them to
        INC A                    ; give the range 1 - 8
        LD B,A                   ; copy it to B and C
        LD C,A
        LD A,1                   ; set bit 0 of A
PLOT10: RRCA                     ; rotate it so that the set bit is
        DJNZ PLOT10              ; in the correct place before
        RET                      ; returning
Y_RAND:
        CALL RAND_NUM            ; get an 8-bit random number and
        CP 192                   ; check to see that it is less than
        JR NC,Y_RAND_NUM         ; 192. Jump back if it isn't until
        RET                      ; we get a valid number less than 192
RAND_NUM:
        LD HL,(SEED_POINTER)     ; the random number is obtained by
        INC HL                   ; poking the Spectrum ROM from £0000
        LD A,H                   ; to £4000
        AND £3F                  ; the pointer to the ROM address is
        LD H,A                   ; incremented each time we call the
        LD (SEED_POINTER),HL     ; random number routine and the
        LD A,(HL)                ; accumulator is loaded with the
        RET                      ; current pointer value before
                                 ; returning

                                 ; this is the 8 bytes of shape data
SHAPE:          BYTE £FF,£81,£81,£9F,£80,£81,£81,£FF
                                ; the random number pointer
SEED_POINTER:
        WORD 2000

                                ; initial values for the 6 shape
                                ; variables
BLOB_VARS:
                WORD £3010,£FF01
                WORD £0950,£00FF
                WORD £0960,£0101
                WORD £9F70,£FF00
                WORD £9F80,£00FF
                WORD £7F10,£00FF
62    1     211   254   33    0     64    17    1     64    1     255   23    117   237   176
205   116   128   221   33    111   129   6     6     197   221   86    1     221   94    0
205   217   128   14    4     221   9     193   16    239   118   1     108   7     11    203
120   40    251   221   33    111   129   6     6     197   205   171   128   21    80    128
1     4     0     221   9     193   16    241   62    127   219   254   31    56    219   201
205   97    128   221   119   2     79    205   97    128   221   119   3     177   40    240
201   205   88    129   254   96    48    3     62    255   201   254   160   56    3     62
1     201   175   201   1     191   255   17    0     0     205   32    129   28    16    250
65    205   32    129   20    16    250   5     250   32    129   29    16    250   65    205
32    129   21    16    250   4     197   205   80    129   87    205   88    129   95    205
42    129   182   119   193   11    203   120   40    236   201   221   86    1     221   94
0     205   217   128   221   126   2     131   95    221   126   3     130   87    205   217
128   56    8     221   114   1     221   115   0     55    201   205   217   128   221   86
1     221   94    0     205   217   128   167   201   213   205   42    129   17    101   129
8     167   8     6     8     197   213   65    26    87    30    0     203   58    203   27
16    250   122   173   119   162   186   40    3     8     55    8     35    123   174   119
163   187   40    3     8     55    8     43    36    124   230   7     32    10    125   198
32    111   56    4     127   214   8     103   209   193   19    16    200   8     209   201
197   213   205   42    129   182   119   209   193   201   122   254   192   208   230   192
31    55    31    31    170   230   248   170   103   123   7     7     7     170   230   199
170   7     7     111   123   230   7     60    71    79    62    1     15    16    253   201
205   88    129   254   197   48    249   201   42    109   129   35    124   230   63    103
34    109   129   126   201   255   129   129   159   128   129   129   255   208   7     16
48    1     255   80    9     255   0     96    9     1     1     112   159   0     255   128
159   255   0     16    127   255   0