How To Write ZX Spectrum Games – Chapter 8

Sprites

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

Converting Pixel Positions to Screen Addresses

UDGs and character graphics are all very fine and dandy, but the better games usually use sprites and there are no handy ROM routines to help us here because Sir Clive didn’t design the Spectrum as a games machine.  Sooner or later a games programmer has to confront the thorny issue of the Spectrum’s awkward screen layout.  It’s a tricky business converting x and y pixel coordinates into a screen address but there are a couple of methods we might employ to do this.

Using a Screen Address Look-up Table

The first method is to use a pre-calculated address table containing the screen address for each of the Spectrum’s 192 lines such as this, or a similar variation:

       xor a               ; clear carry flag and accumulator.
       ld d,a              ; empty de high byte.
       ld a,(ycoord)       ; y position.
       rla                 ; shift left to multiply by 2.
       ld e,a              ; place this in low byte of de pair.
       rl d                ; shift top bit into de high byte.
       ld hl,addtab        ; table of screen addresses.
       add hl,de           ; point to table entry.
       ld e,(hl)           ; low byte of screen address.
       inc hl              ; point to high byte.
       ld d,(hl)           ; high byte of screen address.
       ld a,(xcoord)       ; horizontal position.
       rra                 ; divide by two.
       rra                 ; and again for four.
       rra                 ; shift again to divide by eight.
       and 31              ; mask away rubbish shifted into rightmost bits.
       add a,e             ; add to address for start of line.
       ld e,a              ; new value of e register.
       ret                 ; return with screen address in de.
.
.
addtab defw 16384
       defw 16640
       defw 16896
.
.

On the plus side this is very fast, but it does mean having to store each of the 192 line addresses in a table, taking up 384 bytes which might be better employed elsewhere.

Calculating Screen Addresses

The second method involves calculating the address ourselves and doesn’t require an address look-up table.  In doing this we need to consider three things: Which third of the screen the point is in, the character line to which it is closest, and the pixel line upon which it falls within that cell.  Judicious use of the and operand will help us to decide all three.  It is a complicated business however, so please bear with me as I endeavour to explain how it works.

We can establish which of the three screen segments a point is situated in by taking the vertical coordinate and masking away the six least significant bits to leave a value of 0, 64 or 128 each of the segments being 64 pixels apart.  As the high bytes of the 3 screen segment addresses are 64, 72 and 80 – a difference of 8 going from one segment to another – we take this masked value and divide it by 8 to give us a value of 0, 8 or 16.  We then add 64 to give us the high byte of the screen segment.

Each segment is divided into 8 character cell positions which are 32 bytes apart, so to find that aspect of our address we take the vertical coordinate and mask away the two most significant bits we used to determine the segment along with the three least significant bits which determine the pixel position.  The instruction and 56 will do nicely.  This gives us the character cell position as a multiple of 8, and as the character lines are 32 bytes apart we multiply this by 4 and place our number in the low byte of the screen address.

Finally, character cells are further divided into pixel lines 256 bytes apart, so we again take our vertical coordinate, mask away everything except the bits which determine the line using and 7, and add the result to the high byte.  That will give us our vertical screen address.  From there we take our horizontal coordinate, divide it by 8 and add it to our address.

Here is a routine which returns a screen address for (xcoord, ycoord) in the de register pair.  It could easily be modified to return the address in the hl or bc registers if desired.

scadd  ld a,(ycoord)       ; 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.
       add a,d             ; add to d give segment start address.
       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.

; Add the horizontal element.

       ld a,(xcoord)       ; x coordinate.
       rrca                ; only need to divide by 8.
       rrca
       rrca
       and 31              ; squares 0 - 31 across screen.
       add a,e             ; add to total so far.
       ld e,a              ; de = address of screen.
       ret

Shifting

Once the address has been established we need to consider how our graphics are shifted into position.  The three lowest bit positions of the horizontal coordinate indicate how many pixel shifts are needed.  A slow way to plot a pixel would be to call the scadd routine above, perform an and 7 on the horizontal coordinate, then right shift a pixel from zero to seven times depending on the result before dumping it to the screen.

A shifter sprite routine works in the same way.  The graphic image is taken from memory one line at a time, shifted into position and then placed on the screen before moving to the next line down and repeating the process.  We could write a sprite routine which calculated the screen address for every line drawn, and indeed the first sprite routine I ever wrote worked in such a way.  Fortunately it is much simpler to determine whether we’re moving within a character cell, crossing character cell boundaries, or crossing a segment boundary with a couple of and instructions and to increment or decrement the screen address accordingly.  Put simply, and 63 will return zero if the new vertical position is crossing a segment, and 7 will return zero if it is crossing a character cell boundary and anything else means the new line is within the same character cell as the previous line.

This is a shifter sprite routine which makes use of the earlier scadd routine.  To use it simply set up the coordinates in dispx and dispy, point the bc register pair at the sprite graphic, and call sprite.

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,(dispx)        ; draws sprite (hl).
       ld (tmp1),a         ; store vertical.
       call scadd          ; calculate screen address.
       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 pointer 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.
       add hl,de           ; add to screen address.
       jp sprit6           ; repeat.
sprit5 ld de,63774         ; minus 1762.
       add hl,de           ; subtract 1762 from physical screen address.
       jp sprit6           ; rejoin loop.

As you can see, this routine utilises the xor instruction to merge the sprite onto the screen, which works in the same way that PRINT OVER 1 does in Sinclair BASIC.   The sprite is merged with any graphics already present on screen which can look messy.  To delete a sprite we just display it again and the image magically vanishes.

If we wanted to draw a sprite on top of something that is already on the screen we would need some extra routines, similar to the one above.  One would be required to store the graphics on screen in a buffer so that that portion of the screen could be re-drawn when the sprite is deleted.  The next routine would apply a sprite mask to remove the pixels around and behind the sprite using and or or, then the sprite could finally be applied over the top.  Another routine would be needed to restore the relevant portion of screen to its former state should the sprite be deleted.  However, this would take a lot of CPU time to achieve so my advice would be not to bother unless your game uses something called double buffering – otherwise known as the back screen technique, or you’re using a pre-shifted sprites, which we shall discuss shortly.

Another method you may wish to consider involves making sprites appear to pass behind background objects, a trick you may have seen in Haunted House or Egghead in Space.  While this method is handy for reducing colour clash it requires a sizeable chunk of memory.  In both games a 6K dummy mask screen was located at address 24576, and each byte of sprite data was anded with the data on the dummy screen before being xored onto the physical screen located at address 16384.  Because the physical screen and the dummy mask screen were exactly 8K apart it was possible to flip between them by toggling bit 5 of the h register.  To do this for the sprite routine above our sprit0 routine might look like this:

sprit0 pop hl              ; pop screen address from stack.
       set 5,h             ; address of dummy screen.
       ld a,(hl)           ; what's there already.
       and c               ; mask away parts behind the object.
       res 5,h             ; address of physical screen.
       xor (hl)            ; merge in image data.
       ld (hl),a           ; place onto screen.
       inc l               ; next character cell to right please.
       set 5,h             ; address of dummy screen.
       ld a,(hl)           ; what's there already.
       and d               ; mask with middle bit of image.
       res 5,h             ; address of physical screen.
       xor (hl)            ; merge in image data.
       ld (hl),a           ; put back onto screen.
       inc hl              ; next bit of screen area.
       set 5,h             ; address of dummy screen.
       ld a,(hl)           ; what's already there.
       and e               ; mask right edge of sprite image data.
       res 5,h             ; address of physical screen.
       xor (hl)            ; merge in image data.
       ld (hl),a           ; plonk it on screen.
       ld a,(tmp1)         ; temporary vertical coordinate.

Pre-shifted Sprites

A shifter sprite routine has one major drawback: its lack of speed.  Shifting all that graphic data into position takes time, and if your game needs a lot of sprites bouncing around the screen, you should consider using pre-shifted sprites instead.  This requires eight separate copies of the sprite image, one in each of the shifted pixel positions.  It is then simply a matter of calculating which sprite image to use based on the horizontal alignment of the sprite, calculating the screen address, and copying the sprite image to the screen.  While this method is much faster it is fantastically expensive in memory terms.  A shifter sprite routine requires 32 bytes for an unmasked 16×16 pixel sprite, a pre-shifted sprite requires 256 bytes for the same image.  Writing a Spectrum game is a compromise between speed and available memory.  In general I prefer to move my sprites 2 pixels per frame meaning the odd pixel alignments are not required.  Even so, my pre-shifted sprites still require 128 bytes of precious RAM.

You may not necessarily want the same sprite image in each pre-shifted position.  For example, by changing the position of a sprite’s legs in each of the pre-shifted positions a sprite can be animated to appear as if it is walking from left to right as it moves across the screen.  Remember to match the character’s legs to the number of pixels it is moved each frame.  If you are moving a sprite 2 pixels each frame it is important to make the legs move 2 pixels between frames.  Less than this will make the sprite appear as if it is skating on ice, any more and it will appear to be struggling for grip.  I’ll let you into a little secret here: believe it or not, this can actually affect the way a game feels so getting your animation right is important.

How To Write ZX Spectrum Games – Chapter 7

Basic Alien Collision Detection

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

Coordinate Checking

Coordinate checking should be self-explanatory to most programmers, but is included here for the sake of completeness.  It is also the next step in the development of our Centipede game.

The simplest type of collision detection would be something like this, used to detect if two UDGs have collided:

       ld a,(playx)        ; player's x coordinate.
       cp (ix+1)           ; compare with alien x.
       ret nz              ; not the same, no collision.
       ld a,(playy)        ; player's y coordinate.
       cp (ix+2)           ; compare with alien y.
       ret nz              ; not the same, no collision.
       jp collis           ; we have a collision.

Okay, so that’s pretty simple but most games don’t use single-cell character graphics.  What if the aliens are four character cells wide by two high, and the player’s character is three squares high by three wide?  We need to check if any part of the alien has collided with any part of the player, so we need to check that the coordinates are within a certain range.  If the alien is less than two squares above the player, or less than 3 below him then the vertical coordinates match.  If the alien is also less than four squares to the left of the player, and less than three squares to the right then the horizontal position also matches and we have a collision.

Let’s write some code to do this.  We can start by taking the player’s vertical coordinate:

       ld a,(playx)        ; player's x coordinate.

Then subtract the alien’s vertical position:

      sub (ix+1)          ; subtract alien x.

Next, subtract one from the player’s height, and add it.

      add a,2             ; player is 3 high, so add 3 - 1 = 2.

If the alien is within range the result will be less than the combined height of the player and alien, so we perform the check:

       cp 5                ; combined heights are 3 + 2 = 5.
       ret nc              ; not within vertical range.

Similarly, we can follow this with the code for the horizontal check:

       ld a,(playy)        ; player's y coordinate.
       sub (ix+2)          ; subtract alien y.
       add a,2             ; player is 3 wide, so add 3 - 1 = 2.
       cp 7                ; combined widths are 3 + 4 = 7.
       ret nc              ; not within horizontal range.
       jp collis           ; we have a collision.

Of course, this method doesn’t just work for character-based graphics, it works perfectly well with sprites too, but more of those later. It’s time to finish our game with some collision detection. As our graphics are all single-character UDGs we don’t need anything fancy, a quick x=x and y=y check are all we need.

 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.

       xor a               ; zeroise accumulator.
       ld (dead),a         ; set flag to say player is alive.

; Initialise coordinates.

       ld hl,21+15*256     ; load hl pair with starting coords.
       ld (plx),hl         ; set player coords.
       ld hl,255+255*256   ; player's bullets default.
       ld (pbx),hl         ; set bullet 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.
       push af             ; remember the value.
       call nc,mpu         ; it's being pressed, move up.
       pop af              ; restore accumulator.
       rra                 ; last bit (value 16) reads key 5.
       call nc,fire        ; 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 bullet.  First let's check to see if it's hit anything.

       call bchk           ; check bullet position.

       call dbull          ; delete bullets.
       call moveb          ; move bullets.
       call bchk           ; check new position of bullets.
       call pbull          ; print bullets at new position.

; 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.

       ld a,(dead)         ; was the player killed by a segment?
       and a
       ret nz              ; player killed - lose a life.

; 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

; Fire a missile.

fire   ld a,(pbx)          ; bullet vertical coord.
       inc a               ; 255 is default value, increments to zero.
       ret nz              ; bullet on screen, can't fire again.
       ld hl,(plx)         ; player coordinates.
       dec l               ; 1 square higher up.
       ld (pbx),hl         ; set bullet coords.
       ret

bchk   ld a,(pbx)          ; bullet vertical.
       inc a               ; is it at 255 (default)?
       ret z               ; yes, no bullet on screen.
       ld bc,(pbx)         ; get coords.
       call atadd          ; find attribute here.
       cp 68               ; mushrooms are bright (64) + green (4).
       jr z,hmush          ; hit a mushroom!
       ret

hmush  ld a,22             ; AT control code.
       rst 16
       ld a,(pbx)          ; bullet vertical.
       rst 16
       ld a,(pby)          ; bullet horizontal.
       rst 16
       call wspace         ; set INK colour to white.
kilbul ld a,255            ; x coord of 255 = switch bullet off.
       ld (pbx),a          ; destroy bullet.
       ret

; Move the bullet up the screen 1 character position at a time.

moveb  ld a,(pbx)          ; bullet vertical.
       inc a               ; is it at 255 (default)?
       ret z               ; yes, no bullet on screen.
       sub 2               ; 1 row up.
       ld (pbx),a
       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

pbull  ld a,(pbx)          ; bullet vertical.
       inc a               ; is it at 255 (default)?
       ret z               ; yes, no bullet on screen.
       call bullxy
       ld a,16             ; INK control char.
       rst 16
       ld a,6              ; 6 = yellow.
       rst 16
       ld a,147            ; UDG 'D' is used for player bullets.
       rst 16
       ret

dbull  ld a,(pbx)          ; bullet vertical.
       inc a               ; is it at 255 (default)?
       ret z               ; yes, no bullet on screen.
       call bullxy         ; set up bullet coordinates.
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

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

bullxy ld a,22             ; AT code.
       rst 16
       ld a,(pbx)          ; player bullet vertical coord.
       rst 16              ; set vertical position of player.
       ld a,(pby)          ; bullet's horizontal position.
       rst 16              ; set the horizontal coord.
       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 call segcol         ; segment collision detection.
       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.
       call segcol         ; new segment position collision check.
       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

; Segment collision detection.
; Checks for collisions with player and player's bullets.

segcol ld a,(ply)          ; bullet y position.
       cp (ix+2)           ; is it identical to segment y coord?
       jr nz,bulcol        ; y coords differ, try bullet instead.
       ld a,(plx)          ; player x coord.
       cp (ix+1)           ; same as segment?
       jr nz,bulcol        ; x coords differ, try bullet instead.

; So we have a collision with the player.

killpl ld (dead),a         ; set flag to say that player is now dead.
       ret

; Let's check for a collision with the player's bullet.

bulcol ld a,(pbx)          ; bullet x coords.
       inc a               ; at default value?
       ret z               ; yes, no bullet to check for.
       cp (ix+1)           ; is bullet x coord same as segment x coord?
       ret nz              ; no, so no collision.
       ld a,(pby)          ; bullet y position.
       cp (ix+2)           ; is it identical to segment y coord?
       ret nz              ; no - no collision this time.

; So we have a collision with the player's bullet.

       call dbull          ; delete bullet.
       ld a,22             ; AT code.
       rst 16
       ld a,(pbx)          ; player bullet vertical coord.
       inc a               ; 1 line down.
       rst 16              ; set vertical position of mushroom.
       ld a,(pby)          ; bullet's horizontal position.
       rst 16              ; set the horizontal coord.
       ld a,16             ; ASCII code for INK control.
       rst 16
       ld a,4              ; 4 = colour green.
       rst 16              ; we want all mushrooms in this colour!
       ld a,145            ; UDG 'B' is the mushroom graphic.
       rst 16              ; put mushroom on screen.
       call kilbul         ; kill the bullet.
       ld (ix),a           ; kill the segment.
       ld hl,numseg        ; number of segments.
       dec (hl)            ; decrement it.
       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.
pbx    defb 255            ; player's bullet coordinates.
pby    defb 255
dead   defb 0              ; flag - player dead when non-zero.

; 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.
       defb 0,102,102,102,102,102,102,0    ; player bullet.

; 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.

But wait, why are there two tests for collision detection instead of one? Well, imagine the player’s gunbase is one character cell to the left of a centipede segment. The player is moving right and the segment is moving left. In the next frame the segment would move into the cell occupied by the player, and the player would move into the position occupied by the segment in the previous frame – player and centipede segment would move straight through each other and a single collision detection check would fail to pick this up. By checking for a collision after the player moves, and then again after the centipede segments have moved we can avoid the problem.

Collisions Between Sprites

Fair enough, most Spectrum games use sprites rather than UDGs so in the next chapter we shall see how sprites may be drawn.  For collision detection, the same principle of coordinate checking can be used to detect collisions between sprites.  Subtract the first sprite’s coordinates from those of the second, examine the difference and if it’s within the size range of the two sprites combined we have a collision on that axis.  A simple collision check for two 16×16 pixel sprites might look something like this:

; Check (l, h) for collision with (c, b), strict enforcement.

colx16 ld a,l              ; x coord.
       sub c               ; subtract x.
       add a,15            ; add maximum distance.
       cp 31               ; within x range?
       ret nc              ; no - they've missed.
       ld a,h              ; y coord.
       sub b               ; subtract y.
       add a,15            ; add maximum distance.
       cp 31               ; within y range?
       ret                 ; carry flag set if there's a collision.

There is a drawback with this method. If your sprites don’t entirely fill their 16×16 pixel boundaries then the collision detection will appear to be too strict, and collisions will happen when sprites are close together but not actually touching. A slightly less sensitive check would involve clipping the corners of the sprites into a more octagonal shape, particularly if your sprites have rounded corners. The routine below works by adding the x and y coordinate differences and checking that they are below a certain limit. For a collision between two 16×16 sprites the maximum coordinate distances are 15 pixels for each axis, so by checking that the x and y differences are 25 or less we are effectively shaving a 5x5x5 pixel triangle from each corner.

; Check (l, h) for collision with (c, b), cutting corners.

colc16 ld a,l              ; x coord.
       sub c               ; subtract x.
       jr nc,colc1a        ; result is positive.
       neg                 ; make negative positive.
colc1a cp 16               ; within x range?
       ret nc              ; no - they've missed.
       ld e,a              ; store difference.

       ld a,h              ; y coord.
       sub b               ; subtract y.
       jr nc,colc1b        ; result is positive.
       neg                 ; make negative positive.
colc1b cp 16               ; within y range?
       ret nc              ; no - they've missed.

       add a,e             ; add x difference.
       cp 26               ; only 5 corner pixels touching?
       ret                 ; carry set if there's a collision.

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.

How To Write ZX Spectrum Games – Chapter 5

Simple Background Collision Detection

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

Finding Attributes

Anyone who ever spent time programming in Sinclair BASIC may well remember the ATTR function. This was a way to detect the colour attributes of any particular character cell on the screen, and though tricky for the BASIC programmer to grasp, could be very handy for simple collision detection. The method was so useful in fact that it its machine language equivalent was employed by a number of commercial games, and it is of great use to the novice Spectrum programmer.

There are two ways to find the colour attribute settings for a particular character cell on the Spectrum. A quick look through the Spectrum’s ROM disassembly reveals a routine at address 9603 which will do the job for us, or we can calculate the memory address ourselves.

The simplest way to find an attribute value is to use a couple of ROM routines:

       ld bc,(ballx)       ; put x and y in bc register pair.
       call 9603           ; call ROM to put attribute (c,b) on stack.
       call 11733          ; put attributes in accumulator.

However, it is much faster to do the calculation ourselves. It is also useful to calculate an attribute’s address, and not just its value, in case we want to write to it as well.

Calculating Attribute Addresses

Unlike the Spectrum’s awkward pixel layout, colour cells, located at addresses 22528 to 23295 inclusive, are arranged sequentially in RAM as one would expect. In other words, the screen’s top 32 attribute cells are located at addresses 22528 to 22559 going left to right, the second row of colour cells from 22560 to 22591 and so on. To find the address of a colour cell at print position (x,y) we therefore need only to multiply x by 32, add y, then add 22528 to the result. By then examining the contents of this address we can find out the colours displayed at a particular position, and act accordingly. The following example calculates the address of an attribute at character position (b,c) and returns it in the HL register pair.

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

atadd  ld a,b              ; x position.
       rrca                ; multiply by 32.
       rrca
       rrca
       ld l,a              ; store away in l.
       and 3               ; mask bits for high byte.
       add a,88            ; 88*256=22528, start of attributes.
       ld h,a              ; high byte done.
       ld a,l              ; get x*32 again.
       and 224             ; mask low byte.
       ld l,a              ; put in l.
       ld a,c              ; get y displacement.
       add a,l             ; add to low byte.
       ld l,a              ; hl=address of attributes.
       ld a,(hl)           ; return attribute in a.
       ret

Interrogating the contents of the byte at hl will give the attribute’s value, while writing to the memory location at hl will change the colour of the square.

To make sense of the result we have to know that each attribute is made up of 8 bits which are arranged in this manner:

d0-d2		ink colour 0-7,			0=black, 1=blue, 2=red, 3=magenta,
						4=green, 5=cyan, 6=yellow, 7=white
d3-d5		paper colour 0-7,		0=black, 1=blue, 2=red, 3=magenta,
						4=green, 5=cyan, 6=yellow, 7=white
d6		bright,				0=dull, 1=bright
d7		flash,				0=stable, 1=flashing

 

The test for green paper for example, might involve:

       and 56              ; mask away all but paper bits.
       cp 32               ; is it green(4) * 8?
       jr z,green          ; yes, do green thing.

while checking for yellow ink could be done like this:

       and 7               ; only want bits pertaining to ink.
       cp 6                ; is it yellow (6)?
       jr z,yellow         ; yes, do yellow wotsit.

Applying what we Have Learned to the Game

We can now add an attribute collision check to our Centipede game. As before, the new sections are underlined.

; 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.

       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.

       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

; 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.

How To Write ZX Spectrum Games – Chapter 4

Random Numbers

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

Generating random numbers in machine code can be a tricky problem for a novice programmer.

 First of all, let’s get one thing straight. There is no such thing as a random number generator. The CPU merely follows instructions and has no mind of its own, it cannot simply pluck a number out of thin air based on a whim. Instead, it needs to follow a formula which will produce an unpredictable sequence of numbers which do not appear to follow any sort of pattern, and therefore give the impression of randomness. All we can do is return a false – or pseudo – random number.

 One method of obtaining a pseudo-random number would be to use the Fibonacci sequence, however the easiest and quickest method of generating a pseudo-random 8-bit number on the Spectrum is by stepping a pointer through the ROM, and examining the contents of the byte at each location in turn. There is one small drawback to this method – the Sinclair ROM contains a very uniform and non-random area towards the end which is best avoided. By limiting the pointer to, say, the first 8K of ROM we still have a sequence of 8192 “random” numbers, more than enough for most games. In fact, every game I have ever written with a random number generator uses this method, or a very similar one:

; 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

Let’s put our new random number generator to use in our Centipede game. Every Centipede game needs mushrooms – lots of them – scattered randomly across the play area, and we can now call the random routine to supply coordinates for each mushroom as we display them. The bits underlined are those we need to add.

; 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.

       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.

       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.
       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.
       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.
       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.
       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

; 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

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.

Once run this listing looks more like a Centipede game than it did before, but there’s a major problem. The mushrooms are distributed in a random fashion around the screen, but the player can move straight through them. Some form of collision detection is required to prevent this happening, and we shall cover this in the next chapter.

How To Write ZX Spectrum Games – Chapter 3

Loudspeaker Sound Effects

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

The Loudspeaker

There are two ways of generating sound and music on the ZX Spectrum, the best and most complicated of which is via the AY38912 sound chip in the 128K models. This method is described in detail in a later chapter, but for now we will concern ourselves with the 48K loudspeaker. Simple it may be, but this method does have its uses especially for short sharp sound effects during games.

 

Beep

First of all we need to know how to produce a beep of a certain pitch and duration, and the Sinclair ROM has a fairly accessible routine to do the job for us at address 949, all that is required is to pass the parameters for pitch in the HL register pair and duration in DE, call 949 and we get an appropriate “beep”.

Alas, the way in which we work out the parameters required is a little tricky as it needs a little calculation. We need to know the Hertz value for the frequency of note to emit, essentially just the number of times the loudspeaker needs to be toggled each second to produce the desired pitch. A suitable table is located below (# stands for ‘sharp’):

Middle C 261.63

C# 277.18

D 293.66

D# 311.13

E 329.63

F 349.23

F# 369.99

G 392.00

G# 415.30

A 440.00

A# 466.16

B 493.88

For each octave higher, simply double the frequency, to go an octave lower halve it. For example, to produce a note C one octave higher than middle C we take the value for Middle C – 261.63, and double it to 523.26.

Once the frequency is established we multiply it by the number of seconds required and pass this to the ROM routine in the DE register pair as the duration – so to play the note at middle C for one tenth of a second the duration required would be 261.63 * 0.1 = 26. The pitch is worked out by first dividing the 437500 by the frequency, subtracting 30.125 and passing the result in the HL registers. For middle C this would mean a value of 437500 / 261.63 – 30.125 = 1642.

In other words:

DE = Duration = Frequency * Seconds

HL = Pitch = 437500 / Frequency – 30.125

So to play note G# one octave above that of middle C for one quarter of one second:

; Frequency of G sharp in octave of middle C = 415.30
; Frequency of G sharp one octave higher = 830.60
; Duration = 830.6 / 4 = 207.65
; Pitch = 437500 / 830.6 - 30.125 = 496.6

       ld hl,497           ; pitch.
       ld de,208           ; duration.
       call 949            ; ROM beeper routine.
       ret

Of course, this routine isn’t just useful for musical notes – we can use it for a variety of effects as well, one of my favourites being a simple pitch bend routine:

       ld hl,500           ; starting pitch.
       ld b,250            ; length of pitch bend.
loop   push bc
       push hl             ; store pitch.
       ld de,1             ; very short duration.
       call 949            ; ROM beeper routine.
       pop hl              ; restore pitch.
       inc hl              ; pitch going up.
       pop bc
       djnz loop           ; repeat.
       ret

Have a play with the above routine – by fiddling with it it’s pretty easy to adjust the pitch up and down, and to change the starting frequency and pitch bend and length producing a number of interesting effects. One word of warning though – Don’t go too crazy with your pitch or duration values or the beeper routine will get stuck and you won’t be able to regain control of your Spectrum without resetting it.

White Noise

When using the loudspeaker we don’t even have to stick with the routines in the ROM, it is easy enough to write our own sound effects routines, especially if we want to generate white noise for crashes and bangs. White noise is usually a lot more fun to play with.

To generate white noise all we need is a quick and simple random number generator (a Fibonacci sequence might work, but I’d recommend stepping a pointer through the first 8K of ROM and fetching the byte at each location to get a reasonably random 8-bit number). Then write this value to port 254. Remember this port also controls the border colour so if you don’t want a striped multicolour border effect we need to mask off the border bits with AND 248 and add the number for the border colour we want (1 for blue, 2 for red etc.) before performing an OUT (254) instruction. When we’ve done this we need to put in a small delay loop (short for high pitch, long for lower pitch) and repeat the process a few hundred times. This will give us a nice “crash” effect.

This routine is based on a sound effect from Egghead 3:

noise  ld e,250            ; repeat 250 times.
       ld hl,0             ; start pointer in ROM.
noise2 push de
       ld b,32             ; length of step.
noise0 push bc
       ld a,(hl)           ; next "random" number.
       inc hl              ; pointer.
       and 248             ; we want a black border.
       out (254),a         ; write to speaker.
       ld a,e              ; as e gets smaller...
       cpl                 ; ...we increase the delay.
noise1 dec a               ; decrement loop counter.
       jr nz,noise1        ; delay loop.
       pop bc
       djnz noise0         ; next step.
       pop de
       ld a,e
       sub 24              ; size of step.
       cp 30               ; end of range.
       ret z
       ret c
       ld e,a
       cpl
noise3 ld b,40             ; silent period.
noise4 djnz noise4
       dec a
       jr nz,noise3
       jr noise2

How To Write ZX Spectrum Games – Chapter 2

Keyboard and Joystick Control

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

 

One Key at a Time

Providing that you haven’t disabled or otherwise meddled with the Spectrum’s default interrupt mode the ROM will automatically read the keyboard and update several system variables located at memory location 23552 fifty times per second. The simplest way to check for a keypress is to first load address 23560 with a null value, then interrogate this location until it changes, the result being the ASCII value of the key pressed. This is most useful for those “press any key to continue” situations, for choosing items from a menu and for keyboard input such as high score name entry routines. Such a routine might look like this:

       ld hl,23560         ; LAST K system variable.
       ld (hl),0           ; put null value there.
loop   ld a,(hl)           ; new value of LAST K.
       cp 0                ; is it still zero?
       jr z,loop           ; yes, so no key pressed.
       ret                 ; key was pressed.

Multiple Key-presses

Single key-presses are seldom any use for fast action arcade games however, for this we need to detect more than one simultaneous key-press and this is where things get a little trickier. Instead of reading memory addresses we have to read one of eight ports, each of which corresponds to a row of five keys. Of course, most Spectrum models appear to have far more keys than this so where did they all go? Well actually, they don’t. The original Spectrum keyboard layout consisted of just forty keys, arranged in eight groupings or rows of five. In order to access some of the functions it was necessary to press certain combinations of keys together – for example to delete the combination required was CAPS SHIFT and 0 together. Sinclair added these extra keys when the Spectrum Plus came onto the scene in 1985, and they work by simulating the combinations of key-presses required for the original rubber keyed models.

 The original keyboard layout was separated into these groupings:

Port  Keys

32766 B, N, M, Symbol Shift, Space
49150 H, J, K, L, Enter
57342 Y, U, I, O, P
61438 6, 7, 8, 9, 0
63486 5, 4, 3, 2, 1
64510 T, R, E, W, Q
65022 G, F, D, S, A
65278 V, C, X, Z, Caps Shift

To discover which keys are being pressed we read the appropriate port number, each key in the row being allocated one of the lower five bits d0-d4 (values 1,2,4,8 and 16) where d0 represents the outside key, d4 the innermost. Curiously, each bit is high where it is not pressed, low where it is – the opposite of what you might expect.

To read a row of five keys we simply load the port number into the bc register pair, then perform the instruction in a,(c). As we only need the lowest value bits we can ignore the bits we dont want either with an and 31 or by rotating the bits out of the accumulator into the carry flag using five rra:call c,(address) instructions.

If this is difficult to understand consider the following example:

       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.

Joysticks

Sinclair joystick ports 1 and 2 were simply mapped to each of the rows of number keys and you can easily prove this by going into the BASIC editor and using the joystick to type numbers. Port 1 (Interface 2) was mapped to the keys 6,7,8,9 and 0, Port 2 (Interface 1) to keys 1,2,3,4 and 5. To detect joystick input we simply read the port in the same way as reading the keyboard. Sinclair joysticks use ports 63486 (Interface 1/port 2), and 61438 (Interface 2/port 1), bits d0-d4 will give a 0 for pressed, 1 for not pressed.

The popular Kempston joystick format is not mapped to the keyboard and can be read by using port 31 instead. This means we can use a simple in a,(31). Again, bit values d0-d4 are used although this time the bit settings are as you might expect, with a bit set high if the joystick is being applied in a particular direction. The resulting bit values will be 1 for pressed, 0 for not pressed.

 
; Example joystick control routine.

joycon ld bc,31            ; Kempston joystick port.
       in a,(c)            ; read input.
       and 2               ; check "left" bit.
       call nz,joyl        ; move left.
       in a,(c)            ; read input.
       and 1               ; test "right" bit.
       call nz,joyr        ; move right.
       in a,(c)            ; read input.
       and 8               ; check "up" bit.
       call nz,joyu        ; move up.
       in a,(c)            ; read input.
       and 4               ; check "down" bit.
       call nz,joyd        ; move down.
       in a,(c)            ; read input.
       and 16              ; try the fire bit.
       call nz,fire        ; fire pressed.

A Simple Game

We can now go one step further and, putting into practice what we have already covered, write the main control section for a basic game. This will form the basis of a simple Centipede variant we will be developing over the next few chapters. We haven’t covered everything needed for such a game yet but we can make a start with a small control loop which allows the player to manipulate a small gun base around the screen. Be warned, this program has no exit to BASIC so make sure you’ve saved a copy of your source code before running it.

; 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.

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

; 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.

       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.
       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.
       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.
       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.
       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

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.

Fast, isn’t it? In fact, we’ve slowed the loop down with a halt instruction but it still runs at a speedy 50 frames per second, which is probably a little too fast. Don’t worry, as we add more features to the code it will begin to slow down. If you are feeling confident you might like to try adapting the above program to work with a Kempston joystick. It isn’t difficult, and merely requires changing port 63486 to port 31, and replacing the four subsequent call nc,(address) to call c,(address) (The bits are reversed, remember?)

Redefineable keys are a little more tricky. As you are probably aware, the original Spectrum keyboard was divided into 8 rows of 5 keys each, and by reading the port associated with a particular row of keys, then testing bits d0-d4 we can tell if a particular key is being pressed. If you were to replace ld bc,31 in the code snippet above with ld bc,49150 you could test for the row of keys H to Enter – though that doesn’t make for a convenient redefine keys routine. Thankfully, there is another way of going about it.

We can establish the port required for each row of keys using the formula in the Spectrum manual. Where n is the row number 0-7 the port address will be 254+256*(255-2^n). There’s a ROM routine at address 654 which does a lot of the hard work for us by returning the number of the key pressed in the e register, in the range 0-39. 0-7 correspond to the innermost key of each row in turn (that’s B, H, Y, 6, 5, T, G and V), 8-15 to the next key along in each row up to 39 for the outermost key on the last row – CAPS SHIFT. The shift key status, just for the record, is also returned in d. If no key is pressed then e returns 255.

The ROM routine can only return a single key number which is no good for detecting more than one keypress at a time. To determine whether or not a specific key is being pressed at any time we need to convert the number back into a port and bit, then read that port and check the individual bit for ourselves. There’s a very handy routine I use for the job, and it’s the only routine in my games which I didn’t write myself. Credit for that must go to Stephen Jones, a programmer who used to write excellent articles for the Spectrum Discovery Club many years ago. To use his routine, load the accumulator with the number of the key you wish to test, call ktest, then check the carry flag. If it’s set the key is not being pressed, if there’s no carry then the key is being pressed. If that’s too confusing and seems like the wrong way round, put a ccf instruction just before the ret.

; Mr. Jones' keyboard test routine.

ktest  ld c,a              ; key to test in c.
       and 7               ; mask bits d0-d2 for row.
       inc a               ; in range 1-8.
       ld b,a              ; place in b.
       srl c               ; divide c by 8,
       srl c               ; to find position within row.
       srl c
       ld a,5              ; only 5 keys per row.
       sub c               ; subtract position.
       ld c,a              ; put in c.
       ld a,254            ; high byte of port to read.
ktest0 rrca                ; rotate into position.
       djnz ktest0         ; repeat until we've found relevant row.
       in a,(254)          ; read port (a=high, 254=low).
ktest1 rra                 ; rotate bit out of result.
       dec c               ; loop counter.
       jp nz,ktest1        ; repeat until bit for position in carry.
       ret

How To Write ZX Spectrum Games – Chapter 1

Simple Text and Graphics

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

Introduction

So you’ve read the Z80 documentation, you know how the instructions affect the registers and now you want to put this knowledge to use. Judging by the number of emails I have received asking how to read the keyboard, calculate screen addresses or emit white noise from the beeper it has become clear that there really isn’t much in the way of resources for the new Spectrum programmer. This document, I hope, will grow to fill this void in due course. In its present state it is clearly years from completion, but in publishing the few basic chapters that exist to date I hope it will be of help to other programmers.

The ZX Spectrum was launched in April 1982, and by today’s standards is a primitive machine. In the United Kingdom and a few other countries it was the most popular games machine of the 1980s, and through the joys of emulation many people are enjoying a nostalgic trip back in time with the games of their childhoods. Others are only now discovering the machine for the first time, and some are even taking up the challenge of writing games for this simple little computer. After all, if you can write a decent machine code game for a 1980s computer there probably isn’t much you couldn’t write.

Purists will hate this document, but writing a game isn’t about writing “perfect” Z80 code – as if there were such a thing. A Spectrum game is a substantial undertaking, and you won’t get around to finishing it if you are too obsessed with writing the very best scoring or keyboard reading algorithms. Once you’ve written a routine that works and doesn’t cause problems elsewhere, move on to the next routine. It doesn’t matter if it’s a little messy or inefficient, because the important part is to get the gameplay right. Nobody in his right mind is going to disassemble your code and pick faults with it.

The chapters in this document have been ordered in a way designed to enable the reader to start writing a simple game as soon as possible. Nothing beats the thrill of writing your first full machine-code game, and I have set out this manual in such a way as to cover the very basic minimum requirements for this in the first few chapters. From there we move on to cover more advanced methods which should enable the reader to improve the quality of games he is capable of writing.

Throughout this document a number of assumptions have been made. For a start, it is assumed that the reader is familiar with most Z80 opcodes and what they do. If not there are plenty of guides around which will explain these far better than I could ever do. Learning machine code instructions isn’t difficult, but knowing how to put them together in meaningful ways can be. Familiarity with the load (ld), compare (cp), and conditional jump (jp z / jp c / jp nc) instructions is a good place to start. The rest will fall into place once these are learned.

Tools

These days we have the benefit of more sophisticated hardware, and there is no need to develop software on the machine for which it is intended. There are plenty of adequate cross-assemblers around which will allow Spectrum software to be developed on a PC and the binary file produced can then be imported into an emulator – SPIN is a popular emulator which has support for this feature.

For graphics there’s a tool called SevenUp which I use, and can thoroughly recommend. This can convert bitmaps into Spectrum images, and allows the programmer to specify the order in which sprites or other graphics are sorted. Output can be in the form of a binary image, or source code. Another popular program is TommyGun.

Music wise I’d recommend the SoundTracker utility which can be downloaded from the World of Spectrum archives. There’s a separate compiler program you’ll also need. Bear in mind that these are Spectrum programs, not PC tools and need to be run on an emulator.

As editors and cross-compilers go I am not in a position to recommend the best available, because I use an archaic editor and Z80 Macro cross-assembler written in 1985, running in DOS windows. Neither are tools I would recommend to others. If you require advice on which tools might be suitable for you, I suggest you try the World of Spectrum development forums. This friendly community has a wide range of experience and is always willing to help.

Personal Quirks

Over the many years that I have been writing Spectrum software a number of habits have formed which may seem odd. The way I order my coordinates, for example, does not follow the conventions of mathematics. My machine code programs follow the Sinclair BASIC convention of PRINT AT x,y; where x refers to the number of character cells or pixels from the top of the screen and y is the number of characters or pixels from the left edge. If this seems confusing at first I apologise, but it always seemed a more logical way of ordering things and it just stuck with me. Some of my methodology may seem unusual in places, so where you can devise a better way of doing something by all means go with that instead.

One other thing: commenting your code as you go along is important, if not essential. It can be hellishly difficult trying to find a bug in an uncommented routine you wrote only a few weeks ago. It may seem tedious to have to document every subroutine you write, but it will save development time in the long run. In addition, should you wish to re-use a routine in another game at some point in the future, it will be very easy to rip out the required section and adapt it for your next project.

Other than that, just have fun. If you have any suggestions to make or errors to report, please get in touch.

Jonathan Cauldwell, January 2007.

Hello World

The first BASIC program that most novice programmers write is usually along these lines:

10 PRINT "Hello World"
20 GOTO 10

Alright, so the text may differ. Your first effort may have said “Dave is ace” or “Rob woz ere”, but let’s face it, displaying text and graphics on screen is probably the most important aspect of writing any computer game and – with the exception of pinball or fruit machines – it is practically impossible to conceive a game without a display. With this in mind let us begin this tutorial with some important display routines in the Spectrum ROM.

So how would we go about converting the above BASIC program to machine code? Well, we can PRINT by using the RST 16 instruction – effectively the same as PRINT CHR$ a – but that merely prints the character held in the accumulator to the current channel. To print a string on screen, we need to call two routines – one to open the upper screen for printing (channel 2), then the second to print the string. The routine at ROM address 5633 will open the channel number we pass in the accumulator, and 8252 will print a string beginning at de with length bc to this channel. Once channel 2 is opened, all printing is sent to the upper screen until we call 5633 with another value to send output elsewhere. Other interesting channels are 1 for the lower screen (like PRINT #1 in BASIC, and we can use this to display on the bottom two lines) and 3 for the ZX Printer.

 
       ld a,2              ; upper screen
       call 5633           ; open channel
loop   ld de,string        ; address of string
       ld bc,eostr-string  ; length of string to print
       call 8252           ; print our string
       jp loop             ; repeat until screen is full

string defb '(your name) is cool'
eostr  equ $

Running this listing fills the screen with the text until the scroll? prompt is displayed at the bottom. You will note however, that instead of each line of text appearing on a line of its own as in the BASIC listing, the beginning of each string follows directly on from the end of the previous one which is not exactly what we wanted. To achieve this we need to throw a line ourselves using an ASCII control code. One way of doing this would be to load the accumulator with the code for a new line (13), then use RST 16 to print this code. Another more efficient way is to add this ASCII code to the end of our string thus:

string defb '(your name) is cool'
       defb 13
eostr  equ $

There are a number of ASCII control codes like this which alter the current printing position, colours etc. and experimentation will help you to decide which ones you yourself will find most useful. Here are the main ones I use:

13 NEWLINE sets print position to the beginning of the next line.

16,c INK Sets ink colour to the value of the following byte.

17,c PAPER Sets ink colour to the value of the following byte.

22,x,y AT Sets print x and y coordinates to the values specified in the following two bytes.

Code 22 is particularly handy for setting the coordinates at which a string or graphic character is to be displayed. This example will display an exclamation mark in the bottom right of the screen:

       ld a,2              ; upper screen
       call 5633           ; open channel
       ld de,string        ; address of string
       ld bc,eostr-string  ; length of string to print
       call 8252           ; print our string
       ret
string defb 22,21,31,'!'
eostr  equ $

This program goes one step further and animates an asterisk from the bottom to the top of the screen:

       ld a,2          ; 2 = upper screen.
       call 5633       ; open channel.
       ld a,21         ; row 21 = bottom of screen.
       ld (xcoord),a   ; set initial x coordinate.
loop   call setxy      ; set up our x/y coords.
       ld a,'*'        ; want an asterisk here.
       rst 16          ; display it.
       call delay      ; want a delay.
       call setxy      ; set up our x/y coords.
       ld a,32         ; ASCII code for space.
       rst 16          ; delete old asterisk.
       call setxy      ; set up our x/y coords.
       ld hl,xcoord    ; vertical position.
       dec (hl)        ; move it up one line.
       ld a,(xcoord)   ; where is it now?
       cp 255          ; past top of screen yet?
       jr nz,loop      ; no, carry on.
       ret
delay  ld b,10         ; length of delay.
delay0 halt            ; wait for an interrupt.
       djnz delay0     ; loop.
       ret             ; return.
setxy  ld a,22         ; ASCII control code for AT.
       rst 16          ; print it.
       ld a,(xcoord)   ; vertical position.
       rst 16          ; print it.
       ld a,(ycoord)   ; y coordinate.
       rst 16          ; print it.
       ret

xcoord defb 0
ycoord defb 15

Printing Simple Graphics

Moving asterisks around the screen is all very fine but for even the simplest game we really need to display graphics. Advanced graphics are discussed in later chapters, for now we will only be using simple Space Invader type graphics, and as any BASIC programmer will tell you, the Spectrum has a very simple mechanism for this – the User Defined Graphic, usually abbreviated to UDG.

The Spectrum’s ASCII table contains 21 (19 in 128k mode) user-defined graphics characters, beginning at code 144 and going on up to 164 (162 in 128k mode). In BASIC UDGs are defined by poking data into the UDG area at the top of RAM, but in machine code it makes more sense to change the system variable which points to the memory location at which the UDGs are stored, which is done by changing the two-byte value at address 23675.

We can now modify our moving asterisk program to display a graphic instead with a few changes which are underlined.

       ld hl,udgs      ; UDGs.
       ld (23675),hl   ; set up UDG system variable.
       ld a,2          ; 2 = upper screen.
       call 5633       ; open channel.
       ld a,21         ; row 21 = bottom of screen.
       ld (xcoord),a   ; set initial x coordinate.
loop   call setxy      ; set up our x/y coords.
       ld a,144        ; show UDG instead of asterisk.
       rst 16          ; display it.
       call delay      ; want a delay.
       call setxy      ; set up our x/y coords.
       ld a,32         ; ASCII code for space.
       rst 16          ; delete old asterisk.
       call setxy      ; set up our x/y coords.
       ld hl,xcoord    ; vertical position.
       dec (hl)        ; move it up one line.
       ld a,(xcoord)   ; where is it now?
       cp 255          ; past top of screen yet?
       jr nz,loop      ; no, carry on.
       ret
delay  ld b,10         ; length of delay.
delay0 halt            ; wait for an interrupt.
       djnz delay0     ; loop.
       ret             ; return.
setxy  ld a,22         ; ASCII control code for AT.
       rst 16          ; print it.
       ld a,(xcoord)   ; vertical position.
       rst 16          ; print it.
       ld a,(ycoord)   ; y coordinate.
       rst 16          ; print it.
       ret
xcoord defb 0
ycoord defb 15
udgs   defb 60,126,219,153
       defb 255,255,219,219

As Rolf Harris used to say: “Can you tell what it is yet?”

Of course, there’s no reason why you couldn’t use more than the 21 UDGs if you wished. Simply set up a number of banks of them in memory and point to each one as you need it.

Alternatively, you could redefine the character set instead. This gives a larger range of ASCII characters from 32 (SPACE) to 127 (the copyright symbol). You could even mix text and graphics, redefining the letters and numbers of your font to the style of your choice, then using up the symbols and lowercase letters for aliens, zombies or whatever your game requires. To point to another set we subtract 256 from the address at which the font is placed and place this in the two byte system variable at address 23606. The default Sinclair font for example is located at ROM address 15616, so the system variable at address 23606 points to 15360 when the Spectrum is first switched on.

This code copies the Sinclair ROM font to RAM making it “bolder” as it goes, then sets the system variable to point to it:

       ld hl,15616         ; ROM font.
       ld de,60000         ; address of our font.
       ld bc,768           ; 96 chars * 8 rows to alter.
font1  ld a,(hl)           ; get bitmap.
       rlca                ; rotate it left.
       or (hl)             ; combine 2 images.
       ld (de),a           ; write to new font.
       inc hl              ; next byte of old.
       inc de              ; next byte of new.
       dec bc              ; decrement counter.
       ld a,b              ; high byte.
       or c                ; combine with low byte.
       jr nz,font1         ; repeat until bc=zero.
       ld hl,60000-256     ; font minus 32*8.
       ld (23606),hl       ; point to new font.
       ret

Displaying Numbers

For most games it is better to define the player’s score as a string of ASCII digits, although that does mean more work in the scoring routines and makes high score tables a real pain in the backside for an inexperienced assembly language programmer. We will cover this in a later chapter, but for now we’ll use some handy ROM routines to print numbers for us.

There are two ways of printing a number on the screen, the first of which is to make use of the same routine that the ROM uses to print Sinclair BASIC line numbers. For this we simply load the bc register pair with the number we wish to print, then call 6683:

      ld bc,(score)
       call 6683

However, since BASIC line numbers can go only as high as 9999, this has the disadvantage of only being capable of displaying a four digit number. Once the player’s score reaches 10000 other ASCII characters are displayed in place of numbers. Fortunately, there is another method which goes much higher. Instead of calling the line number display routine we can call the routine to place the contents of the bc registers on the calculator stack, then another routine which displays the number at the top of this stack. Don’t worry about what the calculator stack is and what its function is because it’s of little use to an arcade games programmer, but where we can make use of it we will. Just remember that the following three lines will display a number from 0 to 65535 inclusive:

       ld bc,(score)
       call 11563          ; stack number in bc.
       call 11747          ; display top of calc. stack.

Changing Colours

To set the permanent ink, paper, brightness and flash levels we can write directly to the system variable at 23693, then clear the screen with a call to the ROM:

; We want a yellow screen.
       ld a,49             ; blue ink (1) on yellow paper (6*8).
       ld (23693),a        ; set our screen colours.
       call 3503           ; clear the screen.

The quickest and simplest way to set the border colour is to write to port 254. The 3 least significant bits of the byte we send determine the colour, so to set the border to red:

       ld a,2              ; 2 is the code for red.
       out (254),a         ; write to port 254.

Port 254 also drives the speaker and Mic socket in bits 3 and 4. However, the border effect will only last until your next call to the beeper sound routine in the ROM (more on that later), so a more permanent solution is required. To do this, we simply need to load the accumulator with the colour required and call the ROM routine at 8859. This will change the colour and set the BORDCR system variable (located at address 23624) accordingly. To set a permanent red border we can do this:

       ld a,2              ; 2 is the code for red.
       call 8859           ; set border colour.

Sprite Graphics Tutorial

This tutorial is by Derek M. Smith © 2005.

Sprite graphics are the backbone of arcade games. Put simply, a sprite is a moveable screen object, such as a spaceship, alien, or anything else you can imagine. Some computers come with built in sprites, such as the Commodore and Atari ranges. These machines could generate sprites without the need for complex programming, and on top of this, they used an additional processor, which left the main CPU free to get on with the rest of the program. The Spectrum sadly was never adorned with such luxuries and so programmers had to write their own sprite routines from scratch (a daunting task for the amateur).

Sloppy programming in this area could really spoil a game, either making it too slow, or through flickering graphics.

A simple sprite routine is often one of the first things a new programmer, after having mastered the basics of machine code, will have a go at.

At first it seems straight forward, but soon all sorts of hurdles appear. The first usually being the Spectrum’s unusual screen arrangement. More experienced programmers who have mastered the basic sprite handling routine will then seek ways to optimise it.

This tutorial is aimed at both beginners and the more experienced programmers. Beginners will learn the principles behind sprite programming and the experienced machine coders will learn to find ways of improving their routines. For simplicity, this tutorial assumes that the reader has a decent grasp of assembly language.

ZX Spectrum screen arrangement
“The display file stores the television picture. It is rather curiously laid out…” -Spectrum Manual ch24 p164

On the face of it the Spectrum’s screen arrangement is pretty strange. Just watch the screen of a game loading, and you’ll understand what I mean. Why does it skip lines like that? Sure it may be interesting to watch the picture being gradually built up, but it can be a real pain when it comes to writing a sprite routine (at least until you understand the principles behind its layout).

First off, the Spectrum screen has a resolution of 256 pixels across by 192 down, not including the border around it. It can display 8 colours (including black and white) with two levels of intensity (brightness).

The Memory Map for the screen starts at address 16384 and is 6912 bytes long.
It is split in to two halves with the first 6144 bytes containing the bit-map (or pixel map, if you wish) and the remaining 768 bytes containing the attribute-map.

Consider the bit-map first:-

Each line of 256 pixels is stored as 32 bytes: 32 x 8 = 256. So far so good.

Now you would think that each line would follow on from the one before in the pixel map, and most people (myself included) write their first sprite routine thinking this to be the case – only to find that when they execute the routine, with a shudder of anticipation, their sprite is spread all over the screen. At this point some give up (or decide to write adventure games instead) baffled by the Spectrum’s idiosyncrasies.

Let me say now that when you do grasp the screen layout and the techniques used for addressing it, I think you will be glad that the designers done it that way.

Type in the following and run it:

	10 FOR S = 16384 TO 22527
	20 POKE S, 255
	30 NEXT S

This short BASIC program fills the screen. Although it is moving through memory sequentially, POKING 255 into each memory location, the screen fills up in a rather more esoteric manner. Run the program a few times and watch the pattern it traces. You’ll notice two things: One is the way it skips lines, another is that the screen is divided into three parts.

I said earlier that each line is stored as 32 bytes, with the first line beginning at 16384. Where does the second line start? Most of us assume it would start at 16384+32. In fact it starts at 16384+256 and 16384+32 takes us down to line 8. Remember that the screen is used to display text (characters) as well as graphics. So adding 32 actually takes us down one character row (8 screen lines).

In machine code 16bit addresses, such as 16384, are stored in high byte / low byte format. The high byte is equal to ADDRESS / 256 and the low byte is the remainder from the division, so 16384 in high/low byte format would be 64, 0 (16384 / 256 = 64 with no remainder). This means that when we increase the high byte of a 16 bit address by one, it is equivalent to adding 256. So when it comes to screen addressing all we need to do to move down a line is increase the high byte of the address. This can be done quite easily and quickly in machine code:

	ld hl,16384	;load the hl register pair with the address of the start of the display file
	inc h		;increment the high byte (4 tstates)

This is in fact much quicker than adding 32 where we would have to do the following:

	ld hl,16384
	ld a,l		;load the accumulator with the l register as we cannot add 
			;directly to the l register
	add a,32	;add 32
	ld l,a		;load the result back into the l register (total time 15 tstates)

The above works within a group of 8 lines, ie. 1 char line.

Now, as mentioned before, the other peculiar thing about the screen layout is that it is divided in to three parts – top, middle and bottom. Each third of the screen has 64 lines (or 8 character rows) and takes up 2048 bytes of memory. All that has been said so far applies only so long as we don’t cross from one third into another.

The whole matter becomes at good deal clearer if we look at the screen address in binary.

           High Byte                |               Low Byte

0   1   0   T   T   L   L   L          Cr Cr Cr Cc Cc Cc Cc Cc

I have used some abbreviations to make things a bit clearer:

T – these two bits refer to which third of the screen is being addressed: 00 – Top, 01 – Middle, 10 – Bottom

L – these three bits indicate which line is being addressed: from 0 – 7, or 000 – 111 in binary

Cr – these three bits indicate which character row is being addressed: from 0 – 7

Cc – these five bits refer to which character column is being addressed: from 0 – 31

The top three bits ( 010 ) of the high byte don’t change.

Calculating the screen address

The first task in putting a sprite on the screen is to translate the X,Y coords into a screen address.
There are two ways of doing this. One is to set up a look-up table which contains 192 addresses corresponding to each screen line. The other way is a bit more interesting and involves distilling the appropriate line, column and row bits that make up the address from the X and Y coords. Lets examine this way first.

Taking the Y coord first: This will be in the range 0 – 191, with 0 corresponding to the top of the screen. The lowest three bits indicate which line (within a character row) 0 – 7 we are dealing with. This is the same as the high byte of the screen address. The top two bits refer to which third of the screen we are dealing with:

			   Y Coord

		T   T   -   -   -   L   L   L

Let us assume that the B & C registers contain our X & Y coords. First we need to isolate the lowest three bits of the Y coord (C reg.), as follows:

	ld a,c
	and %00000111		;% indicates that the number following it is in binary format

We will use the HL register pair for the screen address. So next we transfer these three bits to the high byte (H reg.)

	ld h,a

In order to get the top two bits into the correct position we must shift them right three times, as follows:

	ld a,c
	rra			;rotate right accumulator
	rra
	rra

This shifts all the bits to the right three times, with the highest bit being reset after each shift. All that’s left to do at this stage is to clear the bits we don’t need, as follows:

	and %00011000

This must now be ORd with the H register.

	or h

Then the top three bits, which remain constant, are set as appropriate:

	or %01000000
	ld h,a			;load the result back into the H reg.

So now we have the high byte of the screen address.

The low byte of the address is composed of the following bits of both the X and Y coords:

   	  	 X Coord              |               Y Coord

  Cc Cc Cc Cc Cc   -   -   -         -   -   Cr Cr Cr   -   -   -

		Low Byte

  Cr Cr Cr Cc Cc Cc Cc Cc

The character column bits need to be shifted to the right three times.

	ld a,b			;B reg. holds the X coord
	rra
	rra
	rra
	and %00011111		; ensure the top 3 bits are clear
	ld l,a

Then the character row bits must be shifted left twice so that they correspond to the highest three bits of the low byte of the address (see earlier).

	ld a,c			;C reg. holds the Y coord
	rla			;rotate left accumulator
	rla
	and %11100000		;isolate the character row bits
	or l			;OR the result with the low byte
	ld l,a			;and place in L register

Right, that’s the difficult part done. We now have the screen address in HL. All that is left is to get the pixel position (0 – 7 from left to right) from the X coord (lowest three bits).

	ld a,b
	and %00000111

So the complete routine is as follows:

On Entry: B reg = X coord,  C reg = Y coord
On Exit: HL = screen address, A = pixel position

; Calculate the high byte of the screen addressand store in H reg.

	ld a,c
	and %00000111
	ld h,a
	ld a,c
	rra
	rra
	rra
	and %00011000
	or h
	or %01000000
	ld h,a

; Calculate the low byte of the screen address and store in L reg.

	ld a,b
	rra
	rra
	rra
	and %00011111
	ld l,a
	ld a,c
	rla
	rla
	and %11100000
	or l
	ld l,a

; Calculate pixel postion and store in A reg.

	ld a,b
	and %00000111

Using a Look-up Table

The other method of calculating the screen address is to use a table of pre-calculated addresses, and then use the Y coord to pick out the right one. This is a good deal quicker than the above method, albeit at the cost of 384 bytes of memory for the table. The look-up table contains the address of each line in the display file, 192 lines.

Routine to generate a Screen Address Table:

scradtab	equ 64000

gentab		ld de,16384
		ld hl,scradtab
		ld b,192

lineloop	ld (hl),e
		inc l
		ld (hl),d
		inc hl

		inc d
		ld a,d
		and 7
		jr nz,nextline
		ld a,e
		add a,32
		ld e,a
		jr c,nextline
		ld a,d
		sub 8
		ld d,a

nextline	djnz lineloop

		ret

Assuming again that on entry the B and C registers contain our X & Y coords.

	ld de,scradtab		;address of look-up table
	ld l,c			;Y coord in L register
	ld h,0
	add hl,hl		;multiply by two, as each address is 2 bytes
	add hl,de		;add to the start address of the table. HL is now at the
				;appropriate point in the table
	ld a,(hl)		;get low byte of screen address; store in A temporarily
				;so as not to corrupt HL
	inc l
	ld h,(hl)		;get high byte
	ld l,a			;HL now contains the address of the start of the line

	ld a,b			;calculate character column 0 - 31 from X coord
	and %11111000		;isolate appropriate bits
	rrca			;rotate right circular accumulator (faster than srl a)
	rrca
	rrca			;shifting three times is the same as dividing by eight
	add a,l			;add to low byte of address
	ld l,a			;put result back in L reg.

On exit HL will contain the screen address.

An even quicker way is to arrange the lookup table so that it is aligned with a 256 byte page boundary. This method separates the low byte and the high byte of the screen addresses, so in effect there are two tables one containing the low bytes of the address of each screen line and one containing the high bytes. Each must be aligned with a page boundary, ie. the start address of the table must be cleanly divisible by 256.

The above routine for generating the screen address table needs the following changes made to it:

lineloop	ld (hl),e
		inc h
		ld (hl),d
		dec h
		inc l

The table would be filled as follows:

	64000 - 64191		0....... 32....... 64....... 96....... 128.......		Low bytes of screen addresses

	64256 - 64447		64,65,66....71, 64,65,66....71, 64,65,66....71....	High bytes of screen addresses

Finding the start address of a particular line is then done as follows:

	ld h,scradtab/256	; high byte of start of table
	ld l,c			; C reg. contains the Y coord
	ld a,(hl)		; already we have found the low byte, so store in A temporarily
	inc h			; increasing high byte moves forward 256 bytes, and to the
	ld h,(hl)		; corresponding high byte of the screen address		
	ld l,a			; HL now contains the screen address for the start of the line

All that is then needed is to add on the column position, which is calculated in the same way as the previous routine.

Basic considerations

The main factors which need consideration when writing a sprite routine are:

1. Whether or not to use masked sprites
2. Whether or not to use pre-shifted sprites
3. What sizes of sprite to use and whether to use a different routine tailored for each size or a generic
routine which will produce various sizes of sprite

Writing a generic sprite routine which can handle many sizes and types of sprite, is generally shunned by most programmers, because the complexity of the routine has a negative impact on performance. It is perferable to write several routines tailored to specific sizes of sprite.

As per usual there is a trade-off between memory and speed, with the fastest routines using the most memory.

Masked sprites on average will take about 30% more time to process.

Pre-shifted sprites, which are sprites stored in 2, 4 or 8 different pixel positions, are by far the fastest type, but consume lots of memory.

An alternative to using pre-shifted sprites favored by some programmers, is to have a look-up table containing each bit pattern from 0 – 255 shifted into eight positions. In practice this takes up 4K of memory but this is usually less than is required when pre-shifting a large number of sprites. More about this later.

A Worked Example

It’s now time to take a look at how to code a basic sprite routine. The example below is for an 8 x 8 non-masked sprite. The sprite data (a small arrow pointer) is located at USR “A” – 65368. The routine shifts the data in real time. The sprite is XORed with the contents of the screen. You could also OR the sprite with the screen contents (see below for explanation of OR, XOR etc.) Real time rotation of sprite data is easiest acheived with small sprites (16 pixels or less wide), as the data can be stored in registers while being rotated.

The steps in the algorithm to draw the sprite could be stated as follows:

1. Retrieve X and Y coords.
2. Calculate Screen Address based on X,Y coords, using a look-up table
3. Calculate Bit Position from X coord (X coord AND 7 = bit position)
4. Retrieve a line of the sprite graphics data
5. Check if Bit Position is zero
6. If so there is no need to shift the sprite data, so skip the code which shifts the data (jump to step 8 )
7. Shift the sprite data according to its bit position
8. Put the line of sprite data on the screen
9. Adjust screen address for next line
10. Perform steps 4-9 until all lines have been drawn

		ORG 50000

SPRITE	DI			;Disable Interupts. Not strictly necessary in this
					;example as we are not redirecting the Stack.

		LD BC,(XPOS)		; First off, get the X & Y coords
					; and place them in B & C registers

		LD H,SCRADTAB/256	; this next section calculates the
		LD L,B			; screen address using a lookup table
		LD A,(HL)		; as explained earlier in the tutorial
		INC H
		LD H,(HL)
		LD L,A
		LD A,C
		AND 248
		RRCA
		RRCA
		RRCA
		ADD A,L
		LD L,A
		LD (SCRADD),HL	; store screen address for later
					; as HL is needed again

		LD A,C			; find which pixel position the sprite
		AND 7			; will be at and store it. We need this to
		LD (BITPOS),A		; know how many times to shift the sprite data

		LD HL,SPRGFX	; start address for the sprite graphic

		LD C,8			; The sprite is 8 lines tall

LINELOOP	LD E,(HL)		; load the E reg with the first line of
		INC L			; sprite data, and move forward

		PUSH HL		; preserve this address for later

		LD A,(BITPOS)		; Retrieve the pixel position
		OR A			; Quick way of testing if A is zero - Note: A is unaffected
		JR Z,SKIPROTATE	; If zero then no shifting is needed

		LD B,A			; loop counter for number of times to
					; rotate (shift) sprite data
		XOR A			; Quick way setting A to zero and
					; clearing the Carry Flag. The Carry Flag must be reset
					; as the rotate instructions will shift its contents
					; into the sprite data.

ROTATELOOP	RR E			; An extra register is needed for the
		RRA			; shifted sprite data. After the RR E is executed
					; rightmost bit of sprite data is shifted out to the Carry Flag.
					; It is then shifted into the A reg by RRA. So no data is lost.
		DJNZ ROTATELOOP	; Loop back until shifting is complete

SKIPROTATE	LD D,A			; Store in D register, as A will be
					; needed for another purpose. E & D regs now contain the
					; shifted sprite data.

		LD HL,(SCRADD)	; Get back the screen address

		LD A,(HL)		; Actually put a line of the sprite on the screen
		XOR E			
		LD (HL),A
		INC L
		LD A,(HL)
		XOR D
		LD (HL),A

		DEC L			; move back and down one line
		INC H

		LD A,H			; It is not necessary to recalculate
		AND 7			; the screen address for each line of
		JR NZ,A1		; the sprite. All that is needed is to
		LD A,L			; check if a char. or segment boundary
		ADD A,32		; has been crossed and adjust address
		LD L,A			; accordingly.
		JR C,A1
		LD A,H
		SUB 8
		LD H,A

A1		LD (SCRADD),HL	; store it again

		POP HL		; retrieve the address of the next line
					; of sprite data
		DEC C		
		JP NZ,LINELOOP	; loop back until all lines are drawn.

		EI			; Enable Interupts again
		RET

XPOS		DEFB 0
YPOS		DEFB 0
SCRADD	DEFW 0
BITPOS	DEFB 0

SCRADTAB	EQU 64000

On Exit : A, BC, DE, HL corrupt

As can be seen from the above routine it is not necessary to re-calculate the screen address for every line of a sprite. A couple of checks can be made to test if the next line to be drawn is in a new character row or screen third and adjust the address accordingly:

	LD A,H				;HL contains screen address
	AND 7
	JR NZ, REST_OF_PROGRAM
	LD A,L
	ADD A,32
	LD L,A
	JR C, REST_OF PROGRAM
	LD A,H
	SUB 8
	LD H,A

Rest Of Program continues here...

Routine to generate a table of screen addresses

GENTAB	LD DE,16384
		LD HL,SCRADTAB
		LD B,192

LINELOOP	LD (HL),E
		INC H
		LD (HL),D
		DEC H
		INC L

		INC D
		LD A,D
		AND 7
		JR NZ,NEXTLINE
		LD A,E
		ADD A,32
		LD E,A
		JR C,NEXTLINE
		LD A,D
		SUB 8
		LD D,A

NEXTLINE	DJNZ LINELOOP

		RET

Sprite Graphics Data

		ORG 65368

SPRGFX	DEFB %00000000
		DEFB %01111000
		DEFB %01110000
		DEFB %01111000
		DEFB %01011100
		DEFB %00001110
		DEFB %00000100
		DEFB %00000000

OR, XOR, AND

00111100  OR 	00011000  = 00111100

00111100  XOR	00011000  = 00100100

00111100  AND	00011000  = 00011000

Masked Sprites

Masking is a technique used to blank out the area behind a sprite before drawing the sprite to the display. It’s slower than planar (non-masked) sprites, and uses twice as much memory but the final result is more pleasing to the eye. If your sprites are going to be moving over a patterned background its the best technique to use.

The way to draw a masked sprite is to AND the mask with background and then OR the actual sprite with the result. When you AND two bitmaps, bits in the BACKGROUND are reset by zeros in the MASK, and left as they are by ones. That’s probably not very easy to grasp so look at the following examples:

background	            mask

  00111100     and      11111111 	=  00111100	; each bit in the data byte remains  the same after the mask is applied

  00111100     and      00000000 	=  00000000	; all the bits in the data byte are reset by the mask

  11111111     and      00111100 	=  00111100	; the middle four bits remain unchanged and the rest are reset

The previous example showed how to code a very simple sprite routine. Lets now look at a more complex example. This routine will draw a 16 x 16 Masked Sprite, storing the background underneath in a buffer.

This is probably the largest size that can be conveniently shifted in real-time. Any larger and you will want to use a different technique.

The following routine uses a couple of non-standard programming techniques. These tricks help to speed up execution but make the code a bit more difficult to follow. One technique involves using the Stack to retrieve the sprite data, the other is self-modifying code, where the program pokes values into instructions that will be executed later on in the routine.

		ORG 50000,50000

SPRITE	DI				; Disable Interupts.
		LD (SPTEMP),SP		; Store Stack Pointer as it will be redirected
						; by the routine
		LD BC,(XPOS)
		LD A,C
		AND 7
		LD (BITPOS),A

		LD H,SCRADTAB/256		; Calculate screen address as before
		LD L,B
		LD A,(HL)
		INC H
		LD H,(HL)
		LD L,A
		LD A,C
		AND 248
		RRCA
		RRCA
		RRCA
		ADD A,L
		LD L,A

		LD (SCRADD),HL

		LD IX,BKGRNDBUF		; IX is used to point to a temporary buffer where the contents
						; of the screen under the sprite are stored.

		LD SP,SPRGFX		; start address for the sprite graphic

		LD A,16			; The sprite is 16 lines tall
		LD (LINECOUNT),A

LINELOOP	POP DE			; Using the stack to retrieve a line of sprite graphics
		POP HL			; E and D will contain the MASK as it is stored first
						; L and H hold the actual sprite image
		LD A,255			; When shifting, the A reg will be the extra register that the MASK
						; is shifted into. It is loaded with 255 as that means all the bits are
						; initially transparent.
		SCF				; The carry flag is set by this instruction so that bits shifted into the
						; left side of the mask are set (transparent)
		EX AF,AF'			; We also need an extra register for the sprite image, so we will use
						; the alternate or shadow A register.

		LD A,(BITPOS)			; Retrieve the pixel position
		OR A				; Quick way of testing if A is zero
		JR Z,SKIPROTATE		; If zero then no shifting is needed

		LD B,A				; loop counter for number of times to shift

		XOR A				; clear carry flag and set A reg. to zero,
						; as the rotate instructions will shift its contents (carry flag's)
						; into the sprite image data. This is the opposite to the MASK.

ROTATELOOP	EX AF,AF'			; Shift the Sprite Mask data
		RR E				
		RR D
		RRA

		EX AF,AF'			; Shift the Sprite Image data
		RR L
		RR H
		RRA

		DJNZ ROTATELOOP		; Loop back until shifting is complete

SKIPROTATE	LD (DATA+1),A			; This is the piece of self-modifying code mentioned earlier.
		EX AF,AF'			; As the A reg will be needed and there are no other registers
		LD (MASK+1),A			; free, we will poke their data (the rightmost bytes of the sprite mask
						; and image) into instructions further on in the routine.

		LD BC,(SCRADD)		; Get back the screen address

		LD A,(BC)			; Actually put a line of sprite data on the screen
		LD (IX),A			; Store what is under the sprite so that it can be erased
						; without corrupting the background
		AND E				; This instruction applies the MASK
		OR L				; The sprite image is then ORed with the background
		LD (BC),A			; and the result copied back to the screen
		INC C
		INC IXL

		LD A,(BC)			; Put the second byte on the screen
		LD (IX),A			; As we are dealing with a 16 x 16 sprite
		AND D				; each line will be three bytes wide when shifted
		OR H
		LD (BC),A
		INC C
		INC IXL

		LD A,(BC)			; Put the third byte on the screen
		LD (IX),A
MASK		AND 255			; These are the two instructions that were modified
DATA		OR 0				; earlier. The data stored in the A reg. and its shadow
						; were poked into the AND & OR instructions.
		LD (BC),A
		INC IXL

		DEC C
		DEC C				; move back and down one line
		INC B

		LD A,B				; It is not necessary to recalculate
		AND 7				; the screen address for each line of
		JR NZ,A1			; the sprite. All that is needed is to
		LD A,C				; check if a char. or segment (screen third) boundary
		ADD A,32			; has been crossed and adjust the address
		LD C,A				; accordingly.
		JR C,A1
		LD A,B
		SUB 8
		LD B,A

A1		LD (SCRADD),BC		; store it again

		LD HL,LINECOUNT
		DEC (HL)
		JP NZ,LINELOOP		; loop back until all lines are drawn.

		LD SP,(SPTEMP)		; Restore the Stack Pointer
		EI				; Enable Interupts again
		RET

In order to clear the sprite we need to copy back to the screen the Buffer holding the contents
of the screen under the sprite. The following routine will do this.

CLEARSPRITE	DI

		LD BC,(OLDXPOS)

		LD H,SCRADTAB/256
		LD L,B
		LD A,(HL)
		INC H
		LD H,(HL)
		LD L,A
		LD A,C
		AND 248
		RRCA
		RRCA
		RRCA
		ADD A,L
		LD L,A

		LD DE, BKGRNDBUF		

		LD C,16	

CLOOP		LD A,(DE)
		LD (HL),A
		INC L
		INC E

		LD A,(DE)
		LD (HL),A
		INC L
		INC E

		LD A,(DE)
		LD (HL),A
		INC E

		DEC L
		DEC L
		INC H

		LD A,H
		AND 7
		JR NZ,A2
		LD A,L
		ADD A,32
		LD L,A
		JR C,A2
		LD A,H
		SUB 8
		LD H,A

A2		DEC C
		JP NZ,CLOOP

		EI
		RET

XPOS		DEFB 0
YPOS		DEFB 0
OLDXPOS	DEFB 0
OLDYPOS	DEFB 0
LINECOUNT	DEFB 0
BITPOS	DEFB 0
SCRADD	DEFW 0
SPTEMP	DEFW 0

SCRADTAB	EQU 64000
BKGRNDBUF	EQU 64512
; ASM source file created by SevenuP v1.12
; SevenuP (C) Copyright 2002-2004 by Jaime Tejedor G¢mez, aka Metalbrain

;GRAPHIC DATA:
;Pixel Size:      ( 16,  16)
;Char Size:       (  2,   2)
;Sort Priorities: X char, Char line, Y char, Mask
;Attributes:      No attributes
;Mask:            Yes, mask before

SPRGFX	DEFB	248, 31,  0,  0
		DEFB	224,  7,  3,192
		DEFB	192,  3, 15,240
		DEFB	128,  1, 25,248
		DEFB	128,  1, 51,252
		DEFB	  0,  0, 39,252
		DEFB	  0,  0,111,250
		DEFB	  0,  0,127,252

		DEFB	  0,  0,127,250
		DEFB	  0,  0,127,252
		DEFB	  0,  0, 63,248
		DEFB	128,  1, 63,244
		DEFB	128,  1, 31,232
		DEFB	192,  3, 13, 80
		DEFB	224,  7,  2,128
		DEFB	248, 31,  0,  0

Addendum: There appears to be a minor bug in the original code by Derek Smith. Commentator uglifruit posted a fix in the comments section, which has now been integrated into the above code.

Introduction to Z88DK – Part 1: Installation & Compilation

This article was originally published on www.speccy.org/wiki, in Spanish, and is reproduced with permission from Santiago Romero. Translation to English courtesy Google and some re-write from my end. Please do let me know of any errors in the text.


In this series of articles we’ll take a look at Z88DK and learn to make programs with it for our Spectrum.

Basically it is a C cross compiler, which allows us to make our programs in this language on our desktop computer (PC running Windows or Linux, SPARC) and create a file that can be run in an emulator. Using the right tools it can be moved to tape to run on our Spectrum. While these articles will focus on the target machine of this publication (the Sinclair ZX Spectrum), the z88dk allows develop applications for a wide range of platforms including Sam Coupe, MSX1, Jupiter ACE, ZX81, and a large number of computers based on the Z80 processor.

The package includes a set of libraries that allow us to easily add functionality to the programs, including a versatile graphics library, which will enable us to even write a game. The C compiler included with z88dk is only a small compiler accepts many features of ANSI C and perform basic optimizations. Finally, we clarify that the purpose of these articles is not to explain the syntax or the operation of the C language and, the reader is assumed to have a basic knowledge about it.

In this issue we focus on how to install the package for different platforms, as well as learn how to compile and run the examples.

Installation

To download the latest stable version we will go to the download page of z88dk, where we can get the Linux version. Binaries for Windows can be obtained from Dennis Grönign’s page (the link can also be found on the same page z88dk).

Installing on Linux

The first step after downloading the file z88dk-src-xytar.gz (where xy is the version of the package at the time of writing – the latest stable version was 1.5), is unpacking in the directory where you want to have it installed:

tar-xvzf z88dk-src-xytar.gz

Once this is done we must compile the various binaries and libraries that make z88dk:

cd z88dk
. / build.sh

To use z88dk we should declare a number of environment variables. We can do this by creating a file setpath.sh (which we will execute permissions with chmod a + x setpath.sh) with the following contents:

Export Z88DK = "(z88dk) / z88dk"
Export PATH = "$ PATH: $ Z88DK/bin"
Export Z80_OZFILES = "$ Z88DK/lib /"
Export ZCCCFG = "$ Z88DK/lib/config /"

where (z88dk) is the directory where you have unpacked the tar.gz. Every time we use z88dk, must execute this file by using the following command:

setpath.sh source

With this we define the environment variables for the current shell.

One tool that we use in creating our programs is bin2tap. This program transforms the binary produced by the compiler to TAP files that can be run in an emulator. Fortunately, this program comes bundled with z88dk. Unfortunately, this is included without compiling, and also not compiled to run the. / Build.sh previously indicated.

To build it, we move to the $ Z88DK/support/zx /, and execute:

cc-o bin2tap bin2tap.c

That way we will create the executable program. To use it comfortably, we copy the bin directory:

mv $ Z88DK/support/zx/bin2tap $ Z88DK/bin /

Compiling and Running on Linux


Once the package is installed, and prepared our system, we explain how we can compile our programs and run them on any emulator. We shall do this in the examples located in the $ Z88DK/examples/spectrum.

In the above directory are a number of files containing C programs that use z88dk librarie. In the README file have a description for each one of them. Let’s compile and test the gfx.c, which incorporates a series of graphic effects that look spectacular. To do so, according to the README file, we can run:

CCZ + zx-or gfx.bin gfx.c-lndos

Below is a brief description of the syntax:
• Just after the ZCC command specify the target machine, ie the machine which will produce the binary. In our case we want to create a binary for the ZX Spectrum, so we use the modifier + zx (the switches for each of the machines supported can be found at $ Z88DK/doc/compile.txt).

• With the-o parameter specified the resulting binary file name (in our case, gfx.bin).

• Then name the source file.

• Finally, we con-lndos to bind to the bookstore NDOS (in next installments we go into detail about the most useful libraries and functionalities offered). The libraries are in $ Z88DK/libsrc /.

As you can see, the syntax is very similar to Linux gcc compiler, so if we are used to programming in C on this platform, we should not have any problem. The resulting file, gfx.bin, is a binary file (which a text editor can’t understand). Keep in mind that CCZ is not the compiler itself, but it is a frontend that is responsible for implementing the compiler and assembler / linker (all these applications can be found in the $ Z88DK/bin /).

This binary may not yet be executed in any emulator, we must first convert it to a suitable format. To do this we can use the application bin2tap, we compiled in the previous paragraph:

bin2tap gfx.bin gfx.tap

And now we can test the program in our favorite emulator!

Installation on Windows (thanks to the collaboration of Miguel)

This installation has been completed and tested on a PC with Windows 98 Second Edition. Attempt on a Windows XP system files and executables in the bin directory gave the error “xxxxxxx.exe is not a valid win32 file” (This may be due more to the PC on which the author has installed the operating system incompatibility problems between himself and z88dk Windows XP).
The first step we must carry out is to download the file z88dk-win32-xyzip (where xy indicates the version of z88dk) we can find on the project website. Once downloaded, unzip the zip file in the directory c: z88dk, which will be the default directory that we use in this article. We can switch to one that suits us more by changing the path in all explanations given below.

The following adds the path of the executables and libraries to the autoexec.bat file. For this we have two options:

• 1. Add the path manually by editing the autoexec.bat file. To do this simply edit the autoexec.bat file located in c: / with the Windows Notepad, or from a MS-DOS “edit autoexec.bat” and add the following lines:

SET Z80_OZFILES = C:  z88dk  Lib 
SET ZCCCFG = C:  z88dk  Lib  Config 
SET PATH =% PATH% C:  z88dk  Bin

• 2. When you uncompress the zip files containing z88dk, there is a file called SETZ88-W95.inf, we place the mouse pointer over it, right-click and select install, which automatically adds the file path autoexec.bat.
After one of the above, reboot the PC to update the paths.
Compile and run on Windows

When writing code with the z88dk, the code is written in C language in plain text files with your preferred editor (can be even Windows Notepad). These files are usually saved with a .c extension.
To show briefly how we create a file to run on a ZX Spectrum emulator, or upload to a real computer, we’ll use one of the examples that come with z88dk.

• The first thing we do is open a MS-DOS command window and move to the folder where the examples are present:
To put ourselves in the root directory of C:

CD c:
CD z88dk  examples  spectrum

With that we are in the examples folder.
Now we have to compile the program. To do this we will use the executable ZCC.EXE, which is located in the BIN folder, but we do not have to enter it at all, because we added its path earlier. Compile by typing:

CCZ + zx-or gfx.bin gfx.c-lndos

When you run it, it creates a file.bin in which we will have our program. We need to convert it to a format that the emulator can understand, or move to tape and load on a real ZX Spectrum.

• To convert the file from .bin to .tap, we have a program called BIN2TAP.EXE that is included in z88dk and located in the support zx folder.

Usage is very simple: we have to be in the same folder as the. BIN2TAP.exe and provide it the .bin source and specify the target .tap. Execute:

BIN2TAP gfx.bin gfx.tap

And now we have our program ready to load into our favorite emulator.