How To Write ZX Spectrum Games – Chapter 12
Note: This article was originally written by Jonathan Cauldwell and is reproduced here with permission.
The Halt Instruction
We measure the speed of a Spectrum game by the amount of time it takes for a complete pass of the main loop, including all the jobs done by routines called within that loop. The simplest way to introduce a delay is to insert halt instructions to wait for an interrupt at certain points in the main loop to wait for an interrupt. As the Spectrum generates 50 interrupts per second, this means that main loops which have 1, 2 or 3 such pauses will run at 50, 25 or 17 frames per second respectively, so long as the other processing does not take up more than a frame to complete. Generally speaking, it is not a good idea to have the player sprite moving more slowly than 17 frames per second.
Actually, the halt instruction can be quite handy. In effect, it waits for the television scan line to reach the end of the screen. This means that a good time to delete, move then re-display a sprite is immediately after a halt, because the scan line won’t catch up with the image, and there is no chance of flicker. If you have your game’s status panel at the top of the screen, this means there is even further for the scan line to travel before it reaches the sprite area, and you can often squeeze in a couple of sprites after a halt without much danger of flickering.
The halt instruction can also be used in a loop to pause for longer periods. The following code will pause for 100 fiftieths of a second – or two seconds:
ld b,100 ; time to pause. delay halt ; wait for an interrupt. djnz delay ; repeat.
The Spectrum’s Clock
Unfortunately, halt is a blunt instrument. It always waits for the next interrupt, regardless of how long is left before the next one. Imagine a situation where your main loop takes 3/4 of a frame to do its processing most of the time, but every so often has periods where extra processing is involved, taking up an extra 1/2 a frame. Under these circumstances, a halt will keep the game at a constant 50 frames per second for the majority of the time, but as soon as the extra processing kicks in, the first interrupt has passed, and halt will wait for the next one, meaning that the game slows down to 25 frames per second periodically.
There is a way around this problem, and that is to count the number of frames that have elapsed since the last iteration of the main loop. On the Spectrum, the interrupt service routine in the ROM updates the Spectrum’s 24-bit frames counter 50 times per second, as well as doing other things. This counter is stored in the system variables at address 23672, so by checking this location once every iteration of the loop, we can tell how many interrupts have occurred since the last time we were at the same point. Naturally, if you want to write your own interrupt routines you will either have to use rst 56 to update the clock, or increment a frame counter yourself if you wish to use this method.
This routine is designed to stabilise a game to run at a more-or-less constant 25 frames per second:
wait ld hl,pretim ; previous time setting ld a,(23672) ; current timer setting. sub (hl) ; difference between the two. cp 2 ; have two frames elapsed yet? jr nc,wait0 ; yes, no more delay. jp wait wait0 ld a,(23672) ; current timer. ld (hl),a ; store this setting. ret pretim defb 0
Instead of simply sitting in a loop, you could perform some additional non-essential processing. For example, I tend to cycle my sprites around the table I hold them in, changing the order in which they are displayed each loop to help prevent flickering.
Seeding Random Numbers
The Spectrum’s frame counter is useful for something else: it can be used to initialise the seed for random numbers. Using the random number generator in the random numbers chapter, we can do this:
ld a,(23672) ; current timer. ld (seed),a ; set first byte of random seed.
This is fine if we’re working on genuine hardware, and will ensure a game does not start with the same sequence of random numbers every time it is played. Unfortunately, emulator authors have a nasty habit of automatically loading tape files once opened – a practice which not only makes development difficult, it results in the machine always being in the same state every time a particular game is loaded, meaning random numbers can follow the same sequence every time that game is played. The solution for the games programmer is to wait for a debounced keypress as soon as our game has loaded, after which we can set our seed. This introduces a human element and ensures the random number generator is different every time.