Archive

Archive for February, 2013

How To Write ZX Spectrum Games – Chapter 5

February 28, 2013 1 comment

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

How To Write ZX Spectrum Games – Chapter 4

February 28, 2013 Leave a comment

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

February 28, 2013 Leave a comment

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
%d bloggers like this: