Home > Z80 Assembly > How To Write ZX Spectrum Games – Chapter 6

How To Write ZX Spectrum Games – Chapter 6

Tables

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

Aliens Don’t Come One at a Time

Let us say, for the sake of example, we were writing a Space Invaders game featuring eleven columns, each containing five rows of invaders.  It would be impractical to write the code for each of the fifty-five aliens in turn, so we need to set up a table.  In Sinclair BASIC we might go about this by defining three arrays of fifty-five elements – one for the invaders’ x coordinates, one for y coordinates, plus a third status byte.  We could do something similar in assembler by setting up three tables of fifty-five bytes each in memory, then adding the number for each alien to the start of each table to access the individual element.  Unfortunately, that would be slow and cumbersome.

A far better method is to group the three data elements for each invader into a structure, and then have fifty-five of these structures in a table.  We can then point hl to the address of each invader, and know that hl points to the status byte, hl plus one points to the x coordinate, and hl plus two points to the y coordinate.  The code to display an alien might look something like this:

       ld hl,aliens        ; alien data structures.
       ld b,55             ; number of aliens.
loop0  call show           ; show this alien.
       djnz loop0          ; repeat for all aliens.
       ret
show   ld a,(hl)           ; fetch alien status.
       cp 255              ; is alien switched off?
       jr z,next           ; yes, so don't display him.
       push hl             ; store alien address on the stack.
       inc hl              ; point to x coord.
       ld d,(hl)           ; get coord.
       inc hl              ; point to y coord.
       ld e,(hl)           ; get coord.
       call disply         ; display alien at (d,e).
       pop hl              ; retrieve alien address from the stack.
next   ld de,3             ; size of each alien table entry.
       add hl,de           ; point to next alien.
       ret                 ; leave hl pointing to next one.

Using the Index Registers

The drawback with this routine is that we have to be very careful where hl is pointing to all the time, so it might be an idea to store hl in a two-byte temporary memory location before calling show, then restoring it afterwards, adding three at the end of the main loop, then performing the djnz instruction.  If we were writing for the Nintendo GameBoy with its cut-down Z80 this would probably represent our best option.  On machines with more advanced processors such as the Spectrum and CPC464 we can use the index registers, ix, to simplify our code a little.  Because the ix register pair allows us to displace our indirect addressing, we can point ix to the beginning of an alien’s data structure and access all elements within it without the need to change ix again.  Using ix our alien display routine might look like this:

       ld ix,aliens        ; alien data structures.
       ld b,55             ; number of aliens.
loop0  call show           ; show this alien.
       ld de,3             ; size of each alien table entry.
       add ix,de           ; point to next alien.
       djnz loop0          ; repeat for all aliens.
       ret
show   ld a,(ix)           ; fetch alien status.
       cp 255              ; is alien switched off?
       ret z               ; yes, so don't display him.
       ld d,(ix+1)         ; get coord.
       ld e,(ix+2)         ; get coord.
       jp disply           ; display alien at (d,e).

Using ix means we only ever need to point to the beginning of an alien’s data structure, so ix will always return the status for the current invader, ix+1 the x coordinate, and so on.  This method enables the programmer to use complex data structures for his aliens of up to 128 bytes long, without getting confused as to which bit of the structure our registers are pointing at any given time as with the hl example earlier.  Unfortunately, using ix is a little slower than hl, so we shouldn’t use it for the more intensive processing tasks such as manipulating graphics.

Let us apply this method to our Centipede game.  Firstly, we need to decide how many segments are needed, and what data to store about each segment.  In our game the segments will need to move left or right until they hit a mushroom, then move down and go back the other way.  So it seems we will need a flag to indicate the particular direction a segment is travelling in, plus an x or y coordinate.  Our flag can also be used to indicate that a particular segment has been destroyed.  With this in mind we can set up a data structure of three bytes:

centf  defb 0              ; flag, 0=left, 1=right, 255=dead.
centx  defb 0              ; segment x coordinate.
centy  defb 0              ; segment y coordinate.

If we choose to have ten segments in our centipede, we need to reserve a table space of thirty bytes. Each segment needs to be initialised at the beginning, then deleted, moved and redisplayed during the game.

Initialising our segments is probably the simplest task, so we can use a simple loop incrementing the hl register pair for each byte before setting it.  Something like this will usually do the trick:

       ld b,10             ; number of segments to initialise.
       ld hl,segmnt        ; segment table.
segint ld (hl),1           ; start off moving right.
       inc hl
       ld (hl),0           ; start at top.
       inc hl
       ld (hl),b           ; use B register as y coordinate.
       inc hl
       djnz segint         ; repeat until all initialised.

Processing and displaying each segment is going to be slightly more complicated, so for that we will use the ix registers.  We need to write a simple algorithm which manipulates a single segment left or right until it hits a mushroom, then moves down and switches direction.  We’ll call this routine proseg (for “process segment”), and set up a loop which points to each segment in turn and calls proseg.  Providing we get the movement algorithm correct we should then see a centipede snaking its way through the mushrooms.  Applying this to our code is straightforward – we check the flag byte for each segment (ix) to see which way the segment is moving, increment or decrement the horizontal coordinate (ix+2) accordingly, then check the attribute at that character cell.  If it’s green and black we increment the vertical coordinate (ix+1), and switch the direction flag (ix).

Okay, there are one or two other things to consider, such as hitting the sides or bottom of the screen, but that’s just a case of checking the segment’s coordinates and switching direction or moving to the top of the screen when we need to.  The segments also need to be deleted from their old positions prior to being moved, the redisplayed at their new positions, but we have already covered the steps required to perform those tasks.

Our new code looks like this:

; We want a black screen.

       ld a,71             ; white ink (7) on black paper (0),
                           ; bright (64).
       ld (23693),a        ; set our screen colours.
       xor a               ; quick way to load accumulator with zero.
       call 8859           ; set permanent border colours.

; Set up the graphics.

       ld hl,blocks        ; address of user-defined graphics data.
       ld (23675),hl       ; make UDGs point to it.

; Okay, let's start the game.

       call 3503           ; ROM routine - clears screen, opens chan 2.

; Initialise coordinates.

       ld hl,21+15*256     ; load hl pair with starting coords.
       ld (plx),hl         ; set player coords.

       ld b,10             ; number of segments to initialise.
       ld hl,segmnt        ; segment table.
segint ld (hl),1           ; start off moving right.
       inc hl
       ld (hl),0           ; start at top.
       inc hl
       ld (hl),b           ; use B register as y coordinate.
       inc hl
       djnz segint         ; repeat until all initialised.

       call basexy         ; set the x and y positions of the player.
       call splayr         ; show player base symbol.

; Now we want to fill the play area with mushrooms.

       ld a,68             ; green ink (4) on black paper (0),
                           ; bright (64).
       ld (23695),a        ; set our temporary colours.
       ld b,50             ; start with a few.
mushlp ld a,22             ; control code for AT character.
       rst 16
       call random         ; get a 'random' number.
       and 15              ; want vertical in range 0 to 15.
       rst 16
       call random         ; want another pseudo-random number.
       and 31              ; want horizontal in range 0 to 31.
       rst 16
       ld a,145            ; UDG 'B' is the mushroom graphic.
       rst 16              ; put mushroom on screen.
       djnz mushlp         ; loop back until all mushrooms displayed.


; This is the main loop.

mloop  equ $

; Delete the player.

       call basexy         ; set the x and y positions of the player.
       call wspace         ; display space over player.

; Now we've deleted the player we can move him before redisplaying him
; at his new coordinates.

       ld bc,63486         ; keyboard row 1-5/joystick port 2.
       in a,(c)            ; see what keys are pressed.
       rra                 ; outermost bit = key 1.
       push af             ; remember the value.
       call nc,mpl         ; it's being pressed, move left.
       pop af              ; restore accumulator.
       rra                 ; next bit along (value 2) = key 2.
       push af             ; remember the value.
       call nc,mpr         ; being pressed, so move right.
       pop af              ; restore accumulator.
       rra                 ; next bit (value 4) = key 3.
       push af             ; remember the value.
       call nc,mpd         ; being pressed, so move down.
       pop af              ; restore accumulator.
       rra                 ; next bit (value 8) reads key 4.
       call nc,mpu         ; it's being pressed, move up.


; Now he's moved we can redisplay the player.

       call basexy         ; set the x and y positions of the player.
       call splayr         ; show player.

; Now for the centipede segments.

       ld ix,segmnt        ; table of segment data.
       ld b,10             ; number of segments in table.
censeg push bc
       ld a,(ix)           ; is segment switched on?
       inc a               ; 255=off, increments to zero.
       call nz,proseg      ; it's active, process segment.
       pop bc
       ld de,3             ; 3 bytes per segment.
       add ix,de           ; get next segment in ix registers.
       djnz censeg         ; repeat for all segments.

       halt                ; delay.

; Jump back to beginning of main loop.

       jp mloop

; Move player left.

mpl    ld hl,ply           ; remember, y is the horizontal coord!
       ld a,(hl)           ; what's the current value?
       and a               ; is it zero?
       ret z               ; yes - we can't go any further left.

; now check that there isn't a mushroom in the way.

       ld bc,(plx)         ; current coords.
       dec b               ; look 1 square to the left.
       call atadd          ; get address of attribute at this position.
       cp 68               ; mushrooms are bright (64) + green (4).
       ret z               ; there's a mushroom - we can't move there.

       dec (hl)            ; subtract 1 from y coordinate.
       ret

; Move player right.

mpr    ld hl,ply           ; remember, y is the horizontal coord!
       ld a,(hl)           ; what's the current value?
       cp 31               ; is it at the right edge (31)?
       ret z               ; yes - we can't go any further left.

; now check that there isn't a mushroom in the way.

       ld bc,(plx)         ; current coords.
       inc b               ; look 1 square to the right.
       call atadd          ; get address of attribute at this position.
       cp 68               ; mushrooms are bright (64) + green (4).
       ret z               ; there's a mushroom - we can't move there.

       inc (hl)            ; add 1 to y coordinate.
       ret

; Move player up.

mpu    ld hl,plx           ; remember, x is the vertical coord!
       ld a,(hl)           ; what's the current value?
       cp 4                ; is it at upper limit (4)?
       ret z               ; yes - we can go no further then.

; now check that there isn't a mushroom in the way.

       ld bc,(plx)         ; current coords.
       dec c               ; look 1 square up.
       call atadd          ; get address of attribute at this position.
       cp 68               ; mushrooms are bright (64) + green (4).
       ret z               ; there's a mushroom - we can't move there.

       dec (hl)            ; subtract 1 from x coordinate.
       ret

; Move player down.

mpd    ld hl,plx           ; remember, x is the vertical coord!
       ld a,(hl)           ; what's the current value?
       cp 21               ; is it already at the bottom (21)?
       ret z               ; yes - we can't go down any more.

; now check that there isn't a mushroom in the way.

       ld bc,(plx)         ; current coords.
       inc c               ; look 1 square down.
       call atadd          ; get address of attribute at this position.
       cp 68               ; mushrooms are bright (64) + green (4).
       ret z               ; there's a mushroom - we can't move there.

       inc (hl)            ; add 1 to x coordinate.
       ret

; Set up the x and y coordinates for the player's gunbase position,
; this routine is called prior to display and deletion of gunbase.

basexy ld a,22             ; AT code.
       rst 16
       ld a,(plx)          ; player vertical coord.
       rst 16              ; set vertical position of player.
       ld a,(ply)          ; player's horizontal position.
       rst 16              ; set the horizontal coord.
       ret

; Show player at current print position.

splayr ld a,69             ; cyan ink (5) on black paper (0),
                           ; bright (64).
       ld (23695),a        ; set our temporary screen colours.
       ld a,144            ; ASCII code for User Defined Graphic 'A'.
       rst 16              ; draw player.
       ret

wspace ld a,71             ; white ink (7) on black paper (0),
                           ; bright (64).
       ld (23695),a        ; set our temporary screen colours.
       ld a,32             ; SPACE character.
       rst 16              ; display space.
       ret

segxy  ld a,22             ; ASCII code for AT character.
       rst 16              ; display AT code.
       ld a,(ix+1)         ; get segment x coordinate.
       rst 16              ; display coordinate code.
       ld a,(ix+2)         ; get segment y coordinate.
       rst 16              ; display coordinate code.
       ret

proseg ld a,(ix)           ; check if segment was switched off
       inc a               ; by collision detection routine.
       ret z               ; it was, so this segment is now dead.
       call segxy          ; set up segment coordinates.
       call wspace         ; display a space, white ink on black.
       call segmov         ; move segment.
       ld a,(ix)           ; check if segment was switched off
       inc a               ; by collision detection routine.
       ret z               ; it was, so this segment is now dead.
       call segxy          ; set up segment coordinates.
       ld a,2              ; attribute code 2 = red segment.
       ld (23695),a        ; set temporary attributes.
       ld a,146            ; UDG 'C' to display segment.
       rst 16
       ret
segmov ld a,(ix+1)         ; x coord.
       ld c,a              ; GP x area.
       ld a,(ix+2)         ; y coord.
       ld b,a              ; GP y area.
       ld a,(ix)           ; status flag.
       and a               ; is the segment heading left?
       jr z,segml          ; going left - jump to that bit of code.

; so segment is going right then!

segmr  ld a,(ix+2)         ; y coord.
       cp 31               ; already at right edge of screen?
       jr z,segmd          ; yes - move segment down.
       inc a               ; look right.
       ld b,a              ; set up GP y coord.
       call atadd          ; find attribute address.
       cp 68               ; mushrooms are bright (64) + green (4).
       jr z,segmd          ; mushroom to right, move down instead.
       inc (ix+2)          ; no obstacles, so move right.
       ret

; so segment is going left then!

segml  ld a,(ix+2)         ; y coord.
       and a               ; already at left edge of screen?
       jr z,segmd          ; yes - move segment down.
       dec a               ; look right.
       ld b,a              ; set up GP y coord.
       call atadd          ; find attribute address at (dispx,dispy).
       cp 68               ; mushrooms are bright (64) + green (4).
       jr z,segmd          ; mushroom to left, move down instead.
       dec (ix+2)          ; no obstacles, so move left.
       ret

; so segment is going down then!

segmd  ld a,(ix)           ; segment direction.
       xor 1               ; reverse it.
       ld (ix),a           ; store new direction.
       ld a,(ix+1)         ; y coord.
       cp 21               ; already at bottom of screen?
       jr z,segmt          ; yes - move segment to the top.

; At this point we're moving down regardless of any mushrooms that 
; may block the segment's path.  Anything in the segment's way will
; be obliterated.

       inc (ix+1)          ; haven't reached the bottom, move down.
       ret

; moving segment to the top of the screen.

segmt  xor a               ; same as ld a,0 but saves 1 byte.
       ld (ix+1),a         ; new x coordinate = top of screen.
       ret

; Simple 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
       ld a,h
       and 31              ; keep it within first 8k of ROM.
       ld h,a
       ld a,(hl)           ; Get "random" number from location.
       inc hl              ; Increment pointer.
       ld (seed),hl
       ret
seed   defw 0


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

atadd  ld a,c              ; vertical coordinate.
       rrca                ; multiply by 32.
       rrca                ; Shifting right with carry 3 times is
       rrca                ; quicker than shifting left 5 times.
       ld e,a
       and 3
       add a,88            ; 88x256=address of attributes.
       ld d,a
       ld a,e
       and 224
       ld e,a
       ld a,b              ; horizontal position.
       add a,e
       ld e,a              ; de=address of attributes.
       ld a,(de)           ; return with attribute in accumulator.
       ret

plx    defb 0              ; player's x coordinate.
ply    defb 0              ; player's y coordinate.

; UDG graphics.

blocks defb 16,16,56,56,124,124,254,254    ; player base.
       defb 24,126,255,255,60,60,60,60     ; mushroom.
       defb 24,126,126,255,255,126,126,24  ; segment.


; Table of segments.
; Format: 3 bytes per entry, 10 segments.
; byte 1: 255=segment off, 0=left, 1=right.
; byte 2 = x (vertical) coordinate.
; byte 3 = y (horizontal) coordinate.

segmnt defb 0,0,0          ; segment 1.
       defb 0,0,0          ; segment 2.
       defb 0,0,0          ; segment 3.
       defb 0,0,0          ; segment 4.
       defb 0,0,0          ; segment 5.
       defb 0,0,0          ; segment 6.
       defb 0,0,0          ; segment 7.
       defb 0,0,0          ; segment 8.
       defb 0,0,0          ; segment 9.
       defb 0,0,0          ; segment 10.
Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: