## Enemy Movement

Note: This article was originally written by Jonathan Cauldwell and is reproduced here with permission.

So we have our playfield, and can allow the player to manipulate a sprite around it, but what we now need are some enemy sprites for the player to avoid.  A new programmer could struggle here, but it really is far simpler than it first appears.

Patrolling Enemies

The easiest type of enemy to program is that with a fixed algorithm to follow, or a predetermined patrol route.  We covered one such technique in the Centipede game earlier.  Another very simple example is that found in games such as JetSet Willy, where a sprite travels in a single direction until it reaches the end of its patrol, then switches direction and heads back to its starting point, before changing direction again and starting the cycle again.  As you might imagine, these routines are incredibly easy to write.

Firstly we set up our alien structure table with minimum and maximum coordinate positions and the present direction.  It’s generally a good idea to comment these tables so we’ll do that too.

```; Alien data table, 6 bytes per alien.
; ix     = graphic type, ie chicken/Amoebatron etc.
; ix + 1 = direction, 0=up, 1=right, 2=down, 3=left.
; ix + 2 = current x coordinate.
; ix + 3 = current y coordinate.
; ix + 4 = minimum x or y coord, depends on direction.
; ix + 5 = maximum x or y coord, depends on direction.

altab  defb 0,0,0,0,0,0
defb 0,0,0,0,0,0
defb 0,0,0,0,0,0```

Then to manipulate our sprite we might write something like this:

```       ld a,(ix+1)         ; alien movement direction.
rra                 ; rotate low bit into carry.
jr nc,movav         ; no carry = 0 or 2, must be vertical.

; direction is 1 or 3 so it's horizontal.

rra                 ; rotate next bit into carry for test.
jr nc,movar         ; direction 1 = move alien right.

; Move alien left.

moval  ld a,(ix+3)         ; get y coordinate.
sub 2               ; move left.
ld (ix+3),a
cp (ix+4)           ; reached mimimum yet?
jr z,movax          ; yes - change direction.
jr c,movax          ; oops, gone past it.
ret

; Move alien right.

movar  ld a,(ix+3)         ; get y coordinate.
ld (ix+3),a
cp (ix+5)           ; reached maximum yet?
jr nc,movax         ; yes - change direction.
ret

; Move alien vertically.

movav  rra                 ; test direction.
jr c,movad          ; direction 2 is down.

; Move alien up.

movau  ld a,(ix+2)         ; get x coordinate.
sub 2               ; move up.
ld (ix+2),a
cp (ix+4)           ; reached mimimum yet?
jr z,movax          ; yes - change direction.
ret

; Move alien down.

movad  ld a,(ix+2)         ; get x coordinate.
ld (ix+2),a         ; new coordinate.
cp (ix+5)           ; reached maximum yet?
jr nc,movax         ; yes - change direction.
ret

; Change alien direction.

movax  ld a,(ix+1)         ; direction flag.
xor 2               ; switch direction, either
; horizontally or vertically.
ld (ix+1),a         ; set new direction.
ret```

If we wanted to go further we might introduce an extra flag to our table, ix+6, to control the speed of the sprite, and only move it, say, every other frame if the flag is set. While simple to write and easy on memory usage, this sort of movement is rather basic and predictable and of limited use. For more complicated patrolling enemies, for example the alien attack waves in a shoot-em-up, we need tables of coordinates and while the code is again easy to write, coordinate tables quickly chew up memory especially if both x and y coordinates are stored. To access such a table we need two bytes per sprite which act as a pointer to the coordinate table.

A typical section of code could look like this:

```       ld l,(ix+2)         ; pointer low byte, little endian.
ld h,(ix+3)         ; pointer high byte.
ld c,(hl)           ; put x coordinate in c.
inc hl              ; point to y coord.
ld b,(hl)           ; put y coordinate in b.
inc hl              ; point to next position.
ld (ix+2),l         ; next pointer low byte.
ld (ix+3),h         ; next pointer high byte.```

The slightly more complicated example below demonstrates an 8-ship attack wave using a table of vertical coordinates. The horizontal position of each sprite moves left at a constant rate of 2 pixels per frame so there’s no need to bother storing it. It uses the shifter sprite routine from chapter 8 so the sprites are a little flickery, but that’s not important here.

```mloop  halt                ; wait for TV beam.
ld ix,entab         ; point to odd spaceships.
call mship          ; move spaceships.
halt
ld ix,entab+4       ; point to even spaceships.
call mship          ; move even spaceships.
call gwave          ; generate fresh waves.
jp mloop            ; back to start of loop.

; Move enemy spaceships.

mship  ld b,4              ; number to process.
mship0 push bc             ; store count.
ld a,(ix)           ; get pointer low.
ld l,a              ; put into l.
ld h,(ix+1)         ; get high byte.
or h                ; check pointer is set up.
and a               ; is it?
call nz,mship1      ; yes, process it then.
add ix,de           ; point to next enemy.
pop bc              ; restore count.
djnz mship0         ; repeat for all enemies.
ret

mship1 push hl             ; store pointer to coordinate.
call dship          ; delete this ship.
pop hl              ; restore coordinate.
ld a,(hl)           ; fetch next coordinate.
inc hl              ; move pointer on.
ld (ix),l           ; new pointer low byte.
ld (ix+1),h         ; pointer high byte.
ld (ix+2),a         ; set x coordinate.
ld a,(ix+3)         ; fetch horizontal position.
sub 2               ; move left 2 pixels.
ld (ix+3),a         ; set new position.
cp 240              ; reached the edge of the screen yet?
jp c,dship          ; not at the moment, display at new position.
xor a               ; zeroise accumulator.
ld (ix),a           ; clear low byte of pointer.
ld (ix+1),a         ; clear high byte of pointer.
ld hl,numenm        ; number of enemies on screen.
dec (hl)            ; one less with which to cope.
ret

gwave  ld hl,shipc         ; ship counter.
dec (hl)            ; one less.
ld a,(hl)           ; check new value.
cp 128              ; waiting for next attack?
jr z,gwave2         ; attack is imminent so set it up.
ret nc              ; yes.
and 7               ; time to generate a new ship?
ret nz              ; not yet it isn't.
ld ix,entab         ; enemy table.
ld de,4             ; size of each entry.
ld b,8              ; number to check.
gwave0 ld a,(ix)           ; low byte of pointer.
ld h,(ix+1)         ; high byte.
or h                ; are they zero?
jr z,gwave1         ; yes, this entry is empty.
add ix,de           ; point to next ship slot.
djnz gwave0         ; repeat until we find one.
ret
gwave2 ld hl,wavnum        ; present wave number.
ld a,(hl)           ; fetch current setting.
inc a               ; next one along.
and 3               ; start again after 4th wave.
ld (hl),a           ; write new setting.
ret
gwave1 ld hl,numenm        ; number of enemies on screen.
inc (hl)            ; one more to deal with.
ld a,(wavnum)       ; wave number.
ld hl,wavlst        ; wave data pointers.
rlca                ; multiple of 2.
rlca                ; multiple of 4.
ld e,a              ; displacement in e.
ld d,0              ; no high byte.
ld a,(shipc)        ; ship counter.
and 8               ; odd or even attack?
rrca                ; make multiple of 2 accordingly.
rrca
ld e,a              ; displacement in e.
ld d,0              ; no high byte.
add hl,de           ; point to first or second half of attack.
ld e,(hl)           ; low byte of attack pointer.
inc hl              ; second byte.
ld d,(hl)           ; high byte of attack pointer.
ld (ix),e           ; low byte of pointer.
ld (ix+1),d         ; high byte.
ld a,(de)           ; fetch first coordinate.
ld (ix+2),a         ; set x.
ld (ix+3),240       ; start at right edge of screen.

; Display enemy ships.

dship  ld hl,shipg         ; sprite address.
ld b,(ix+3)         ; y coordinate.
ld c,(ix+2)         ; x coordinate.
ld (xcoord),bc      ; set up sprite routine coords.
jp sprite           ; call sprite routine.
shipc  defb 128            ; plane counter.
numenm defb 0              ; number of enemies.

; Attack wave coordinates.
; Only the vertical coordinate is stored as the ships all move left
; 2 pixels every frame.

coord0 defb 40,40,40,40,40,40,40,40
defb 40,40,40,40,40,40,40,40
defb 42,44,46,48,50,52,54,56
defb 58,60,62,64,66,68,70,72
defb 72,72,72,72,72,72,72,72
defb 72,72,72,72,72,72,72,72
defb 70,68,66,64,62,60,58,56
defb 54,52,50,48,46,44,42,40
defb 40,40,40,40,40,40,40,40
defb 40,40,40,40,40,40,40,40
defb 38,36,34,32,30,28,26,24
defb 22,20,18,16,14,12,10,8
defb 6,4,2,0,2,4,6,8
defb 10,12,14,16,18,20,22,24
defb 26,28,30,32,34,36,38,40
coord1 defb 136,136,136,136,136,136,136,136
defb 136,136,136,136,136,136,136,136
defb 134,132,130,128,126,124,122,120
defb 118,116,114,112,110,108,106,104
defb 104,104,104,104,104,104,104,104
defb 104,104,104,104,104,104,104,104
defb 106,108,110,112,114,116,118,120
defb 122,124,126,128,130,132,134,136
defb 136,136,136,136,136,136,136,136
defb 136,136,136,136,136,136,136,136
defb 138,140,142,144,146,148,150,152
defb 154,156,158,160,162,164,166,168
defb 170,172,174,176,174,172,170,168
defb 166,164,162,160,158,156,154,152
defb 150,148,146,144,142,140,138,136

; List of attack waves.

wavlst defw coord0,coord0,coord1,coord1
defw coord1,coord0,coord0,coord1

wavnum defb 0              ; current wave pointer.

; Spaceship sprite.

shipg  defb 248,252,48,24,24,48,12,96,24,48,31,243,127,247,255,247
defb 255,247,127,247,31,243,24,48,12,96,24,48,48,24,248,252

sprit7 xor 7               ; complement last 3 bits.
inc a               ; add one for luck!
sprit3 rl d                ; rotate left...
rl c                ; ...into middle byte...
rl e                ; ...and finally into left character cell.
dec a               ; count shifts we've done.
jr nz,sprit3        ; return until all shifts complete.

; Line of sprite image is now in e + c + d, we need it in form c + d + e.

ld a,e              ; left edge of image is currently in e.
ld e,d              ; put right edge there instead.
ld d,c              ; middle bit goes in d.
ld c,a              ; and the left edge back into c.
jr sprit0           ; we've done the switch so transfer to screen.

sprite ld a,(xcoord)       ; draws sprite (hl).
ld (tmp1),a         ; store vertical.
ld a,16             ; height of sprite in pixels.
sprit1 ex af,af'           ; store loop counter.
push de             ; store screen address.
ld c,(hl)           ; first sprite graphic.
inc hl              ; increment poiinter to sprite data.
ld d,(hl)           ; next bit of sprite image.
inc hl              ; point to next row of sprite data.
ld (tmp0),hl        ; store in tmp0 for later.
ld e,0              ; blank right byte for now.
ld a,b              ; b holds y position.
and 7               ; how are we straddling character cells?
jr z,sprit0         ; we're not straddling them, don't bother shifting.
cp 5                ; 5 or more right shifts needed?
jr nc,sprit7        ; yes, shift from left as it's quicker.
and a               ; oops, carry flag is set so clear it.
sprit2 rr c                ; rotate left byte right...
rr d                ; ...through middle byte...
rr e                ; ...into right byte.
dec a               ; one less shift to do.
jr nz,sprit2        ; return until all shifts complete.
sprit0 pop hl              ; pop screen address from stack.
ld a,(hl)           ; what's there already.
xor c               ; merge in image data.
ld (hl),a           ; place onto screen.
inc l               ; next character cell to right please.
ld a,(hl)           ; what's there already.
xor d               ; merge with middle bit of image.
ld (hl),a           ; put back onto screen.
inc hl              ; next bit of screen area.
ld a,(hl)           ; what's already there.
xor e               ; right edge of sprite image data.
ld (hl),a           ; plonk it on screen.
ld a,(tmp1)         ; temporary vertical coordinate.
inc a               ; next line down.
ld (tmp1),a         ; store new position.
and 63              ; are we moving to next third of screen?
jr z,sprit4         ; yes so find next segment.
and 7               ; moving into character cell below?
jr z,sprit5         ; yes, find next row.
dec hl              ; left 2 bytes.
dec l               ; not straddling 256-byte boundary here.
inc h               ; next row of this character cell.
sprit6 ex de,hl            ; screen address in de.
ld hl,(tmp0)        ; restore graphic address.
ex af,af'           ; restore loop counter.
dec a               ; decrement it.
jp nz,sprit1        ; not reached bottom of sprite yet to repeat.
ret                 ; job done.
sprit4 ld de,30            ; next segment is 30 bytes on.
jp sprit6           ; repeat.
sprit5 ld de,63774         ; minus 1762.
jp sprit6           ; rejoin loop.
scadd  ld a,(xcoord)       ; fetch vertical coordinate.
ld e,a              ; store that in e.

; Find line within cell.

and 7               ; line 0-7 within character square.
add a,64            ; 64 * 256 = 16384 = start of screen display.
ld d,a              ; line * 256.

; Find which third of the screen we're in.

ld a,e              ; restore the vertical.
and 192             ; segment 0, 1 or 2 multiplied by 64.
rrca                ; divide this by 8.
rrca
rrca                ; segment 0-2 multiplied by 8.
ld d,a

; Find character cell within segment.

ld a,e              ; 8 character squares per segment.
rlca                ; divide x by 8 and multiply by 32,
rlca                ; net calculation: multiply by 4.
and 224             ; mask off bits we don't want.
ld e,a              ; vertical coordinate calculation done.

ld a,(ycoord)       ; y coordinate.
rrca                ; only need to divide by 8.
rrca
rrca
and 31              ; squares 0 - 31 across screen.
ld e,a              ; de = address of screen.
ret

xcoord defb 0              ; display coord.
ycoord defb 0              ; display coord.
tmp0   defw 0              ; workspace.
tmp1   defb 0              ; temporary vertical position.

; Enemy spaceship table, 8 entries x 4 bytes each.

entab  defb 0,0,0,0
defb 0,0,0,0
defb 0,0,0,0
defb 0,0,0,0
defb 0,0,0,0
defb 0,0,0,0
defb 0,0,0,0
defb 0,0,0,0```

Intelligent Aliens

So far we have dealt with predictable drones, but what if we want to give the player the illusion that enemy sprites are thinking for themselves?  One way we could start to do this would be to give them an entirely random decision making process.

Here is the source code for Turbomania, a game originally written for the 1K coding competition in 2005.  It’s very simple, but incorporates purely random movement.  Enemy cars travel in a direction until they can no longer move, then select another direction at random.  Additionally, a car may change direction at random even if it can continue in its present direction.  It’s very primitive of course, just take a look at the mcar routine and you’ll see exactly what I mean.

```       org 24576

; Constants.

YELLOW equ 49              ; dull yellow attribute.
YELLOB equ YELLOW + 64     ; bright yellow attribute.

; Main game code.

; Clear the screen to give green around the edges.

ld hl,23693         ; system variable for attributes.
ld (hl),36          ; want green background.

waitk  ld a,(23560)        ; read keyboard.
cp 32               ; is SPACE pressed?
jr nz,waitk         ; no, wait.
call nexlev         ; play the game.
jr waitk            ; SPACE to restart game.

; Clear down level data.

nexlev call 3503           ; clear the screen.
ld hl,rmdat         ; room data.
ld de,rmdat+1
ld (hl),1           ; set up a shadow block.
ld bc,16            ; length of room minus first byte.
ldir                ; copy to rest of first row.
ld bc,160           ; length of room minus first row.
ld (hl),b           ; clear first byte.
ldir                ; clear room data.

; Set up the default blocks.

ld c,15             ; last block position.
popbl0 ld b,9              ; last row.
popbl1 call filblk         ; fill the block.
dec b               ; one column up.
jr z,popbl2         ; done column, move on.
dec b               ; and again.
jr popbl1
popbl2 dec c               ; move on row.
jr z,popbl3         ; done column, move on.
dec c               ; next row.
jr popbl0

; Now draw the bits unique to this level.

popbl3 ld b,7              ; number of blocks to insert.
popbl5 push bc             ; store counter.
call random         ; get a random number.
and 6               ; even numbers in range 0-6 please.
add a,2             ; make it 2-8.
ld b,a              ; that's the column.
popbl4 call random         ; another number.
and 14              ; even numbers 0-12 wanted.
cp 14               ; higher than we want?
jr nc,popbl4        ; yes, try again.
inc a               ; place it in range 1-13.
ld c,a              ; that's the row.
call filblk         ; fill block.
popbl6 call random         ; another random number.
and 14              ; only want 0-8.
cp 9                ; above number we want?
jr nc,popbl6        ; try again.
inc a               ; make it 1-9.
ld b,a              ; vertical coordinate.
call random         ; get horizontal block.
and 14              ; even, 0-14.
ld c,a              ; y position.
call filblk         ; fill in that square.
pop bc              ; restore count.
djnz popbl5         ; one less to do.

xor a               ; zero.
ld hl,playi         ; player's intended direction.
ld (hl),a           ; default direction.
inc hl              ; point to display direction.
ld (hl),a           ; default direction.
inc hl              ; player's next direction.
ld (hl),a           ; default direction.
out (254),a         ; set border colour while we're at it.
call atroom         ; show current level layout.

ld hl,168+8*256     ; coordinates.
ld (encar2+1),hl    ; set up second car position.
ld h,l              ; y coord at right.
ld (encar1+1),hl    ; set up first car position.
ld l,40             ; x at top of screen.
ld (playx),hl       ; start player off here.
ld hl,encar1        ; first car.
call scar           ; show it.
ld hl,encar2        ; second car.
call scar           ; show it.
call dplayr         ; show player sprite.
call blkcar         ; make player's car black.

; Two-second delay before we start.

ld b,100            ; delay length.
waitt  halt                ; wait for interrupt.
djnz waitt          ; repeat.

mloop  halt                ; electron beam in top left.
call dplayr         ; delete player.

; Make attributes blue ink again.

call gpatts         ; get player attributes.
defb 17,239,41      ; remove green paper, add blue paper + ink.
call attblk         ; set road colours.

; Move player's car.

ld a,(playd)        ; player direction.
ld bc,(playx)       ; player coordinates.
call movc           ; move coords.
ld hl,(dispx)       ; new coords.
ld (playx),hl       ; set new player position.

; Can we change direction?

ld a,(playi)        ; player's intended direction.
ld bc,(playx)       ; player coordinates.
call movc           ; move coords.
call z,setpn        ; set player's new direction.

; Change direction.

ld a,(nplayd)       ; new player direction.
ld (playd),a        ; set current direction.

call dplayr         ; redisplay at new position.

; Set attributes of car.

call blkcar         ; make player car black.

; Controls.

ld a,239            ; keyboard row 6-0 = 61438.
ld e,1              ; direction right.
rra                 ; player moved right?
call nc,setpd       ; yes, set player direction.
ld e,3              ; direction left.
rra                 ; player moved left?
call nc,setpd       ; yes, set player direction.
ld a,247            ; 63486 is port for row 1-5.
ld e,0              ; direction up.
and 2               ; check 2nd key in (2).
call z,setpd        ; set direction.
ld a,251            ; 64510 is port for row Q-T.
ld e,2              ; direction down.
and e               ; check 2nd key from edge (W)..
call z,setpd        ; set direction.

; Enemy cars.

ld hl,encar1        ; enemy car 1.
push hl             ; store pointer.
call procar         ; process the car.
pop hl              ; restore car pointer.
call coldet         ; check for collisions.
ld hl,encar2        ; enemy car 2.
push hl             ; store pointer.
halt                ; synchronise with display.
call procar         ; process the car.
pop hl              ; restore car pointer.
call coldet         ; check for collisions.

; Count remaining yellow spaces.

ld bc,704           ; attributes to count.
ld a,YELLOB         ; attribute we're seeking.
cpir                ; count characters.
ld a,b              ; high byte of result.
or c                ; combine with low byte.
jp z,nexlev         ; none left, go to next level.

; End of main loop.

jp mloop

; Black car on cyan paper.

blkcar call gpatts         ; get player attributes.
defb 17,232,40      ; remove red paper/blue ink, add blue paper.

; Set 16x16 pixel attribute block.

attblk call attlin         ; paint horizontal line.
call attlin         ; paint another line.
ld a,c              ; vertical position.
and 7               ; is it straddling cells?
ret z               ; no, so no third line.

attlin call setatt         ; paint the road.
call setatt         ; and again.
ld a,b              ; horizontal position.
jr z,attln0         ; no, leave third cell as it is.
call setatt         ; set attribute.
dec l               ; back one cell again.
attln0 push de             ; preserve colours.
ld de,30            ; distance to next one.
add hl,de           ; point to next row down.
pop de              ; restore colour masks.
ret

; Set single cell attribute.

setatt ld a,(hl)           ; fetch attribute cell contents.
and e               ; remove colour elements in c register.
or d                ; add those in b to form new colour.
ld (hl),a           ; set colour.
inc l               ; next cell.
ret

; Collision detection, based on coordinates.

coldet call getabc         ; get coords.
ld a,(playx)        ; horizontal position.
sub c               ; compare against car x.
jr nc,coldt0        ; result was positive.
neg                 ; it was negative, reverse sign.
coldt0 cp 16               ; within 15 pixels?
ret nc              ; no collision.
ld a,(playy)        ; player y.
sub b               ; compare against car y.
jr nc,coldt1        ; result was positive.
neg                 ; it was negative, reverse sign.
coldt1 cp 16               ; within 15 pixels?
ret nc              ; no collision.
pop de              ; remove return address from stack.
ret

setpd  ex af,af'
ld a,e              ; direction.
ld (playi),a        ; set intended direction.
ex af,af'
ret

setpn  ld a,(playi)        ; new intended direction.
ld (nplayd),a       ; set next direction.
ret

; Move coordinates of sprite in relevant direction.

movc   ld (dispx),bc       ; default position.
and a               ; direction zero.
jr z,movcu          ; move up.
dec a               ; direction 1.
jr z,movcr          ; move up.
dec a               ; direction 2.
jr z,movcd          ; move up.
movcl  dec b               ; left one pixel.
dec b               ; left again.
movc0  call chkpix         ; check pixel attributes.
ld (dispx),bc       ; new coords.
ret
movcu  dec c               ; up a pixel.
dec c               ; and again.
jr movc0
movcr  inc b               ; right one pixel.
inc b               ; right again.
jr movc0
movcd  inc c               ; down a pixel.
inc c               ; once more.
jr movc0

; Check pixel attributes for collision.
; Any cells with green ink are solid.

and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
inc hl              ; next square to the right.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
inc hl              ; next square to the right.
ld a,b              ; horizontal position.
jr z,chkpx1         ; no, look down next.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
chkpx1 ld de,30            ; distance to next cell down.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
inc hl              ; next square to the right.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
inc hl              ; next square to the right.
ld a,b              ; horizontal position.
jr z,chkpx2         ; no, look down next.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
chkpx2 ld a,c              ; distance from top of screen.
and 7               ; are we straddling cells vertically?
ret z               ; no, move is therefore okay.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
inc hl              ; next square to the right.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.
inc hl              ; next square to the right.
ld a,b              ; horizontal position.
ret z               ; no, move is fine.
ld a,(hl)           ; get attributes.
and 4               ; check ink colours.
jr nz,chkpx0        ; invalid, block the move.

chkpx0 pop de              ; remove return address from stack.
ret

; Fill a block in the map.

filblk ld a,b              ; row number.
rlca                ; multiply by 16.
rlca
rlca
rlca
ld e,a              ; that's displacement low.
ld d,0              ; no high byte required.
ld hl,rmdat         ; room data address.

; Block address is in hl, let's fill it.

ld (hl),2           ; set block on.
ld de,16            ; distance to next block down.
ld a,(hl)           ; check it.
and a               ; is it set yet?
ret nz              ; yes, don't overwrite.
ld (hl),1           ; set the shadow.
ret

; Draw a screen consisting entirely of attribute blocks.

atroom ld hl,rmdat         ; room data.
ld a,1              ; start at row 1.
ld (dispx),a        ; set up coordinate.
ld b,11             ; row count.
atrm0  push bc             ; store counter.
ld b,15             ; column count.
ld a,1              ; column number.
ld (dispy),a        ; set to left of screen.
atrm1  push bc             ; store counter.
ld a,(hl)           ; get next block type.
push hl             ; store address of data.
rlca                ; double block number.
rlca                ; and again for multiple of 4.
ld e,a              ; displacement to block address.
ld d,0              ; no high byte required.
ld hl,blkatt        ; block attributes.
add hl,de           ; point to block we want.
ldi                 ; transfer first block.
ldi                 ; and the second.
ld bc,30            ; distance to next row.
ex de,hl            ; switch cell and screen address.
add hl,bc           ; point to next row down.
ex de,hl            ; switch them back.
ldi                 ; do third cell.
ldi                 ; fourth attribute cell.
ld hl,dispy         ; column number.
inc (hl)            ; move across one cell.
inc (hl)            ; and another.
pop hl              ; restore room address.
pop bc              ; restore column counter.
inc hl              ; point to next block.
djnz atrm1          ; do rest of row.
inc hl              ; skip one char so lines are round 16.
ld a,(dispx)        ; vertical position.
add a,2             ; look 2 cells down.
ld (dispx),a        ; new row.
pop bc              ; restore column counter.
djnz atrm0          ; do remaining rows.
ret

; Background block attributes.

blkatt defb YELLOB,YELLOB  ; space.
defb YELLOB,YELLOB
defb YELLOB,YELLOB
defb 124,68         ; black/white chequered flag pattern.
defb 68,124

; Calculate address of attribute for character at (dispx, dispy).

atadd  push hl             ; need to preserve hl pair.
ld hl,(dispx)       ; coords to check, in char coords.
add hl,hl           ; multiply x and y by 8.
ld b,h              ; copy y coord to b.
ld c,l              ; put x coord in c.
ex de,hl            ; put address in de.
pop hl              ; restore hl.
ret

; Get player attributes.

gpatts ld bc,(playx)       ; player coordinates.

; Calculate address of attribute for pixel at (c, b).

ataddp ld a,c              ; Look at the vertical first.
rlca                ; divide by 64.
rlca                ; quicker than 6 rrca operations.
ld l,a              ; store in l register for now.
and 3               ; mask to find segment.
add a,88            ; attributes start at 88*256=22528.
ld h,a              ; that's our high byte sorted.
ld a,l              ; vertical/64 - same as vertical*4.
and 224             ; want a multiple of 32.
ld l,a              ; vertical element calculated.
ld a,b              ; get horizontal position.
rra                 ; divide by 8.
rra
rra
and 31              ; want result in range 0-31.
ld l,a              ; that's the low byte done.
ld a,(hl)           ; get cell contents.
ret                 ; attribute address now in hl.

; Move car - change of direction required.

mcarcd ld a,(hl)           ; current direction.
inc a               ; turn clockwise.
and 3               ; only 4 directions.
ld (hl),a           ; new direction.

; Move an enemy car.

mcar   push hl             ; preserve pointer to car.
call getabc         ; fetch coordinates and direction.
call movc           ; move the car.
pop hl              ; refresh car pointer.
jr nz,mcarcd        ; can't move there, turn around.
inc hl              ; point to x.
ld a,c              ; store x pos in c.
ld (hl),a           ; x position.
inc hl              ; point to y.
ld (hl),b           ; new placing.
or b                ; combine the two.
and 31              ; find position straddling cells.
cp 8                ; are we at a valid turning point?
ret nz              ; no, can't change direction.
ld a,r              ; crap random number.
cp 23               ; check it's below this value.
ret nc              ; it isn't, don't change.
push hl             ; store car pointer.
call random         ; get random number.
pop hl              ; restore car.
dec hl              ; back to x coordinate.
dec hl              ; back again to direction.
and 3               ; direction is in range 0-3.
ld (hl),a           ; new direction.
ret

; Fetch car coordinates and direction.

getabc ld a,(hl)           ; get direction.
inc hl              ; point to x position.
ld c,(hl)           ; x coordinate.
inc hl              ; point to y.
ld b,(hl)           ; y position.
ret

; Process car to which hl points.

procar push hl             ; store pointer.
push hl             ; store pointer.
call scar           ; delete car.
pop hl              ; restore pointer to car.
call mcar           ; move car.
pop hl              ; restore pointer to car.

; Show enemy car.

scar   call getabc         ; get coords and direction.
jr dplay0

; Display player sprite.

dplayr ld bc,(playx)       ; player coords.
ld a,(playd)        ; player direction.
dplay0 rrca                ; multiply by 32.
rrca
rrca
ld e,a              ; sprite * 32 in low byte.
ld d,0              ; no high byte.
ld hl,cargfx        ; car graphics.
jr sprite           ; show the sprite.

; This is the sprite routine and expects coordinates in (c ,b) form,
; where c is the vertical coord from the top of the screen (0-176), and
; b is the horizontal coord from the left of the screen (0 to 240).
; Sprite data is stored as you'd expect in its unshifted form as this
; routine takes care of all the shifting itself.  This means that sprite
; handling isn't particularly fast but the graphics only take 1/8th of the
; space they would require in pre-shifted form.

; On entry HL must point to the unshifted sprite data.

sprit7 xor 7               ; complement last 3 bits.
inc a               ; add one for luck!
sprit3 rl d                ; rotate left...
rl c                ; ...into middle byte...
rl e                ; ...and finally into left character cell.
dec a               ; count shifts we've done.
jr nz,sprit3        ; return until all shifts complete.

; Line of sprite image is now in e + c + d, we need it in form c + d + e.

ld a,e              ; left edge of image is currently in e.
ld e,d              ; put right edge there instead.
ld d,c              ; middle bit goes in d.
ld c,a              ; and the left edge back into c.
jr sprit0           ; we've done the switch so transfer to screen.

sprite ld (dispx),bc       ; store coords in dispx for now.
ld a,16             ; height of sprite in pixels.
sprit1 ex af,af'           ; store loop counter.
push de             ; store screen address.
ld c,(hl)           ; first sprite graphic.
inc hl              ; increment poiinter to sprite data.
ld d,(hl)           ; next bit of sprite image.
inc hl              ; point to next row of sprite data.
ld (sprtmp),hl      ; store it for later.
ld e,0              ; blank right byte for now.
ld a,b              ; b holds y position.
and 7               ; how are we straddling character cells?
jr z,sprit0         ; we're not straddling them, don't bother shifting.
cp 5                ; 5 or more right shifts needed?
jr nc,sprit7        ; yes, shift from left as it's quicker.
and a               ; oops, carry flag is set so clear it.
sprit2 rr c                ; rotate left byte right...
rr d                ; ...through middle byte...
rr e                ; ...into right byte.
dec a               ; one less shift to do.
jr nz,sprit2        ; return until all shifts complete.
sprit0 pop hl              ; pop screen address from stack.
ld a,(hl)           ; what's there already.
xor c               ; merge in image data.
ld (hl),a           ; place onto screen.
inc l               ; next character cell to right please.
ld a,(hl)           ; what's there already.
xor d               ; merge with middle bit of image.
ld (hl),a           ; put back onto screen.
inc l               ; next bit of screen area.
ld a,(hl)           ; what's already there.
xor e               ; right edge of sprite image data.
ld (hl),a           ; plonk it on screen.
ld a,(dispx)        ; vertical coordinate.
inc a               ; next line down.
ld (dispx),a        ; store new position.
and 63              ; are we moving to next third of screen?
jr z,sprit4         ; yes so find next segment.
and 7               ; moving into character cell below?
jr z,sprit5         ; yes, find next row.
dec l               ; left 2 bytes.
dec l               ; not straddling 256-byte boundary here.
inc h               ; next row of this character cell.
sprit6 ex de,hl            ; screen address in de.
ld hl,(sprtmp)      ; restore graphic address.
ex af,af'           ; restore loop counter.
dec a               ; decrement it.
jp nz,sprit1        ; not reached bottom of sprite yet to repeat.
ret                 ; job done.
sprit4 ld de,30            ; next segment is 30 bytes on.
jp sprit6           ; repeat.
sprit5 ld de,63774         ; minus 1762.
jp sprit6           ; rejoin loop.

; This routine returns a screen address for (c, b) in de.

scadd  ld a,c              ; get vertical position.
and 7               ; line 0-7 within character square.
add a,64            ; 64 * 256 = 16384 (Start of screen display)
ld d,a              ; line * 256.
ld a,c              ; get vertical again.
rrca                ; multiply by 32.
rrca
rrca
and 24              ; high byte of segment displacement.
ld d,a              ; that's the high byte sorted.
ld a,c              ; 8 character squares per segment.
rlca                ; 8 pixels per cell, mulplied by 4 = 32.
rlca                ; cell x 32 gives position within segment.
and 224             ; make sure it's a multiple of 32.
ld e,a              ; vertical coordinate calculation done.
ld a,b              ; y coordinate.
rrca                ; only need to divide by 8.
rrca
rrca
and 31              ; squares 0 - 31 across screen.
ld e,a              ; hl = address of screen.
ret

; Pseudo-random number generator.
; Steps a pointer through the ROM (held in seed), returning the contents
; of the byte at that location.

random ld hl,(seed)        ; pointer to ROM.
res 5,h             ; stay within first 8K of ROM.
ld a,(hl)           ; get "random" number from location.
xor l               ; more randomness.
inc hl              ; increment pointer.
ld (seed),hl        ; new position.
ret

; Sprite graphic data.
; Going up first.

cargfx defb 49,140,123,222,123,222,127,254,55,236,15,240,31,248,30,120
defb 29,184,108,54,246,111,255,255,247,239,246,111,103,230,3,192

; Second image looks right.

defb 60,0,126,14,126,31,61,223,11,238,127,248,252,254,217,127
defb 217,127,252,254,127,248,11,238,61,223,126,31,126,14,60,0

; Third is pointing down.

defb 3,192,103,230,246,111,247,239,255,255,246,111,108,54,29,184
defb 30,120,31,248,15,240,55,236,127,254,123,222,123,222,49,140

; Last car looks left.

defb 0,60,112,126,248,126,251,188,119,208,31,254,127,63,254,155
defb 254,155,127,63,31,254,119,208,251,188,248,126,112,126,0,60

; Variables used by the game.

org 32768

playi  equ \$               ; intended direction when turn is possible.
playd  equ playi+1         ; player's current direction.
nplayd equ playd+1         ; next player direction.
playx  equ nplayd+1        ; player x.
playy  equ playx+1         ; player's y coordinate.

encar1 equ playy+1         ; enemy car 1.
encar2 equ encar1+3        ; enemy car 2.

dispx  equ encar2+3        ; general-use coordinates.
dispy  equ dispx+1
seed   equ dispy+1         ; random number seed.
sprtmp equ seed+2          ; sprite temporary address.
termin equ sprtmp+2        ; end of variables.

rmdat  equ 49152```

If you have assembled this game and tried it out you will realise that it quickly becomes boring. It is very easy to stay out of the reach of enemy cars to cover one side of the track, then wait until the cars move and cover the other side. There is no hunter-killer aspect in this algorithm so the player is never chased down. What’s more, this routine is so simple cars will reverse direction without warning. In most games this is only acceptable if a sprite reaches a dead end and cannot move in any other direction.

Perhaps we should instead be writing routines where aliens interact with the player, and home in on him. Well, the most basic algorithm would be something along the lines of a basic x/y coordinate check, moving an alien sprite towards the player. The routine below shows how this might be achieved, the homing routine almov is the one which moves the chasing sprite around. Try guiding the number 1 block around the screen with keys ASD and F, and the number 2 block will follow you around the screen. However, in doing this we soon discover the basic flaw with this type of chase – it is very easy to trap the enemy sprite in a corner because the routine isn’t intelligent enough to move backwards in order to get around obstacles.

```; Randomly cover screen with yellow blocks.

ld de,1000          ; address in ROM.
ld b,64             ; number of cells to colour.
yello0 push bc             ; store register.
ld a,(de)           ; get first random coord.
and 127             ; half height of screen.
add a,32            ; at least 32 pixels down.
ld c,a              ; x coord.
inc de              ; next byte of ROM.
ld a,(de)           ; fetch value.
inc de              ; next byte of ROM.
ld b,a              ; y coord.
ld (hl),48          ; set attributes.
pop bc              ; restore loop counter.
djnz yello0         ; repeat a few times.

ld ix,aldat         ; alien data.
call dal            ; display alien.
call dpl            ; display player.

mloop  halt                ; wait for electron beam.
call dal            ; delete alien.
call almov          ; alien movement.
call dal            ; display alien.
halt                ; wait for electron beam.
call dpl            ; delete player.
call plcon          ; player controls.
call dpl            ; display the player.
jp mloop            ; back to start of main loop.

aldat  defb 0,0,0          ; alien data.

; Display/delete alien.

dal    ld c,(ix)           ; vertical position.
ld b,(ix+1)         ; horizontal position.
ld (xcoord),bc      ; set sprite coordinates.
ld hl,algfx         ; alien graphic.
jp sprite           ; xor sprite onto screen.

; Display/delete player sprite.

dpl    ld bc,(playx)       ; coordinates.
ld (xcoord),bc      ; set the display coordinates.
ld hl,plgfx         ; player graphic.
jp sprite           ; xor sprite on or off the screen.

; Player control.

plcon  ld bc,65022         ; port for keyboard row.
ld b,a              ; store result in b register.
rr b                ; check outermost key.
call nc,mpl         ; player left.
rr b                ; check next key.
call nc,mpr         ; player right.
rr b                ; check next key.
call nc,mpd         ; player down.
rr b                ; check next key.
call nc,mpu         ; player up.
ret
mpl    ld hl,playy         ; coordinate.
ld a,(hl)           ; check value.
and a               ; at edge of screen?
ret z               ; yes, can't move that way.
sub 2               ; move 2 pixels.
ld (hl),a           ; new setting.
ret
mpr    ld hl,playy         ; coordinate.
ld a,(hl)           ; check value.
cp 240              ; at edge of screen?
ret z               ; yes, can't move that way.
add a,2             ; move 2 pixels.
ld (hl),a           ; new setting.
ret
mpu    ld hl,playx         ; coordinate.
ld a,(hl)           ; check value.
and a               ; at edge of screen?
ret z               ; yes, can't move that way.
sub 2               ; move 2 pixels.
ld (hl),a           ; new setting.
ret
mpd    ld hl,playx         ; coordinate.
ld a,(hl)           ; check value.
cp 176              ; at edge of screen?
ret z               ; yes, can't move that way.
add a,2             ; move 2 pixels.
ld (hl),a           ; new setting.
ret

; Alien movement routine.

almov  ld a,(playx)        ; player x coordinate.
ld c,(ix)           ; alien x.
ld b,(ix+1)         ; alien y.
cp c                ; check alien x.
jr z,alv0           ; they're equal, do horizontal.
jr c,alu            ; alien is below, move up.
ald    inc c               ; alien is above, move down.
jr alv0             ; now check position for walls.
alu    dec c               ; move down.
alv0   call alchk          ; check attributes.
cp 56               ; are they okay?
jr z,alv1           ; yes, set x coord.
ld c,(ix)           ; restore old x coordinate.
jr alh              ; now do horizontal.
alv1   ld (ix),c           ; new x coordinate.
alh    ld a,(playy)        ; player horizontal.
cp b                ; check alien horizontal.
jr z,alok           ; they're equal, check for collision.
jr c,all            ; alien is to right, move left.
alr    inc b               ; alien is to left, move right.
jr alok             ; check for walls.
all    dec b               ; move right.
alok   call alchk          ; check attributes.
cp 56               ; are they okay?
ret nz              ; no, don't set new y coord.
ld (ix+1),b         ; set new y.
ret

; Check attributes at alien position (c, b).

ld a,3              ; cells high.
alchk0 ex af,af'           ; store loop counter.
ld a,(hl)           ; check cell colour.
cp 56               ; is it black on white?
ret nz              ; no, can't move here.
inc hl              ; cell right.
ld a,(hl)           ; check cell colour.
cp 56               ; is it black on white?
ret nz              ; no, can't move here.
inc hl              ; cell right.
ld a,(hl)           ; check cell colour.
cp 56               ; is it black on white?
ret nz              ; no, can't move here.
ld de,30            ; distance to next cell down.
ex af,af'           ; height counter.
dec a               ; one less to go.
jr nz,alchk0        ; repeat for all rows.
ld (ix),c           ; set new x.
ld (ix+1),b         ; set new y.
ret

; Calculate address of attribute for pixel at (c, b).

ataddp ld a,c              ; Look at the vertical first.
rlca                ; divide by 64.
rlca                ; quicker than 6 rrca operations.
ld l,a              ; store in l register for now.
and 3               ; mask to find segment.
add a,88            ; attributes start at 88*256=22528.
ld h,a              ; that's our high byte sorted.
ld a,l              ; vertical/64 - same as vertical*4.
and 224             ; want a multiple of 32.
ld l,a              ; vertical element calculated.
ld a,b              ; get horizontal position.
rra                 ; divide by 8.
rra
rra
and 31              ; want result in range 0-31.
ld l,a              ; that's the low byte done.
ret                 ; attribute address now in hl.

playx  defb 80             ; player coordinates.
playy  defb 120
xcoord defb 0              ; general-purpose coordinates.
ycoord defb 0

; Shifter sprite routine.

sprit7 xor 7               ; complement last 3 bits.
inc a               ; add one for luck!
sprit3 rl d                ; rotate left...
rl c                ; ...into middle byte...
rl e                ; ...and finally into left character cell.
dec a               ; count shifts we've done.
jr nz,sprit3        ; return until all shifts complete.

; Line of sprite image is now in e + c + d, we need it in form c + d + e.

ld a,e              ; left edge of image is currently in e.
ld e,d              ; put right edge there instead.
ld d,c              ; middle bit goes in d.
ld c,a              ; and the left edge back into c.
jr sprit0           ; we've done the switch so transfer to screen.

sprite ld a,(xcoord)       ; draws sprite (hl).
ld (tmp1),a         ; store vertical.
ld a,16             ; height of sprite in pixels.
sprit1 ex af,af'           ; store loop counter.
push de             ; store screen address.
ld c,(hl)           ; first sprite graphic.
inc hl              ; increment poiinter to sprite data.
ld d,(hl)           ; next bit of sprite image.
inc hl              ; point to next row of sprite data.
ld (tmp0),hl        ; store in tmp0 for later.
ld e,0              ; blank right byte for now.
ld a,b              ; b holds y position.
and 7               ; how are we straddling character cells?
jr z,sprit0         ; we're not straddling them, don't bother shifting.
cp 5                ; 5 or more right shifts needed?
jr nc,sprit7        ; yes, shift from left as it's quicker.
and a               ; oops, carry flag is set so clear it.
sprit2 rr c                ; rotate left byte right...
rr d                ; ...through middle byte...
rr e                ; ...into right byte.
dec a               ; one less shift to do.
jr nz,sprit2        ; return until all shifts complete.
sprit0 pop hl              ; pop screen address from stack.
ld a,(hl)           ; what's there already.
xor c               ; merge in image data.
ld (hl),a           ; place onto screen.
inc l               ; next character cell to right please.
ld a,(hl)           ; what's there already.
xor d               ; merge with middle bit of image.
ld (hl),a           ; put back onto screen.
inc hl              ; next bit of screen area.
ld a,(hl)           ; what's already there.
xor e               ; right edge of sprite image data.
ld (hl),a           ; plonk it on screen.
ld a,(tmp1)         ; temporary vertical coordinate.
inc a               ; next line down.
ld (tmp1),a         ; store new position.
and 63              ; are we moving to next third of screen?
jr z,sprit4         ; yes so find next segment.
and 7               ; moving into character cell below?
jr z,sprit5         ; yes, find next row.
dec hl              ; left 2 bytes.
dec l               ; not straddling 256-byte boundary here.
inc h               ; next row of this character cell.
sprit6 ex de,hl            ; screen address in de.
ld hl,(tmp0)        ; restore graphic address.
ex af,af'           ; restore loop counter.
dec a               ; decrement it.
jp nz,sprit1        ; not reached bottom of sprite yet to repeat.
ret                 ; job done.
sprit4 ld de,30            ; next segment is 30 bytes on.
jp sprit6           ; repeat.
sprit5 ld de,63774         ; minus 1762.
jp sprit6           ; rejoin loop.
scadd  ld a,(xcoord)       ; fetch vertical coordinate.
ld e,a              ; store that in e.

; Find line within cell.

and 7               ; line 0-7 within character square.
add a,64            ; 64 * 256 = 16384 = start of screen display.
ld d,a              ; line * 256.

; Find which third of the screen we're in.

ld a,e              ; restore the vertical.
and 192             ; segment 0, 1 or 2 multiplied by 64.
rrca                ; divide this by 8.
rrca
rrca                ; segment 0-2 multiplied by 8.
ld d,a

; Find character cell within segment.

ld a,e              ; 8 character squares per segment.
rlca                ; divide x by 8 and multiply by 32,
rlca                ; net calculation: multiply by 4.
and 224             ; mask off bits we don't want.
ld e,a              ; vertical coordinate calculation done.

ld a,(ycoord)       ; y coordinate.
rrca                ; only need to divide by 8.
rrca
rrca
and 31              ; squares 0 - 31 across screen.
ld e,a              ; de = address of screen.
ret

tmp0   defw 0
tmp1   defb 0

plgfx  defb 127,254,255,255,254,127,252,127,248,127,248,127,254,127,254,127
defb 254,127,254,127,254,127,254,127,248,31,248,31,255,255,127,254
algfx  defb 127,254,254,63,248,15,240,135,227,231,231,231,255,199,255,15
defb 252,31,248,127,241,255,227,255,224,7,224,7,255,255,127,254```

The best alien movement routines use a combination of random elements and hunter-killer algorithms. In order to overcome the problem in the listing above we need an extra flag to indicate the enemy’s present state or in this case its direction. We can move the sprite along in a certain direction until it becomes possible to switch course vertically or horizontally, whereupon a new direction is selected depending upon the player’s position. However, should it not be possible to move in the desired direction we go in the opposite direction instead. Using this method a sprite can find its own way around most mazes without getting stuck too often. In fact, to absolutely guarantee that the sprite will not get stuck we can add a random element so that every so often the new direction is chosen on a random basis rather than the difference in x and y coordinates.

Cranking up the Difficulty Levels

The weighting applied to the direction-changing decision will determine the sprite’s intelligence levels.  If the new direction has a 90% chance of being chosen on a random basis and a 10% chance based on coordinates the alien will wander around aimlessly for a while and only home in on the player slowly.  That said, a random decision can sometimes be the right one when chasing the player.  An alien on a more difficult screen might have a 60% chance of choosing a new direction randomly, and a 40% chance of choosing the direction based on the player’s relative position.  This alien will track the player a little more closely.  By tweaking these percentage levels it is possible to determine difficulty levels throughout a game and ensure a smooth transition from the simplest of starting screens to fiendishly difficult final levels.