Note: This article was originally written by Jonathan Cauldwell and is reproduced here with permission.
Let us say that we want to write a single screen maze game. We need to display the walls around which the player’s sprite is to be manipulated, and the best way to do this is to create a table of blocks which are transferred to the screen sequentially. As we step through the table we find the address of the block graphic, calculate the screen address and dump the character to the screen.
We will start with the character display routine. Unlike a sprite routine we need to deal with character positions, and luckily it is easier to calculate a screen address for a character position than it is for individual pixels.
There are 24 vertical and 32 horizontal character cell positions on the Spectrum screen, so our coordinates will be between (0,0) and (23,31). Rows 0-7 fall in the first segment, 8-15 in the middle section and positions 16-23 in the third portioin of the screen. As luck would have it, the high byte of the screen address for each segment increases by 8 from one segment to the next, so by taking the vertical cell number and performing an and 24 we immediately get the displacement to the start of relevant segment’s screen address right there. Add 64 for the start of the Spectrum’s screen and we have the high byte of our address. We then need to find the correct character cell within each segment, so we again take the vertical coordinate, and this time use and 7 to determine which of the seven rows we’re trying to find. We multiply this by the character width of the screen – 32 – and add the horizontal cell number to find the low byte of the screen address. A suitable example is below:
; Return character cell address of block at (b, c). chadd ld a,b ; vertical position. and 24 ; which segment, 0, 1 or 2? add a,64 ; 64*256 = 16384, Spectrum's screen memory. ld d,a ; this is our high byte. ld a,b ; what was that vertical position again? and 7 ; which row within segment? rrca ; multiply row by 32. rrca rrca ld e,a ; low byte. ld a,c ; add on y coordinate. add a,e ; mix with low byte. ld e,a ; address of screen position in de. ret
Once we have our screen address it is a straightforward process to dump the character onto the screen. As long as we are not crossing character cell boundaries the next screen line will always fall 256 bytes after its predecessor, so we increment the high byte of the address to find the next line.
; Display character hl at (b, c). char call chadd ; find screen address for char. ld b,8 ; number of pixels high. char0 ld a,(hl) ; source graphic. ld (de),a ; transfer to screen. inc hl ; next piece of data. inc d ; next pixel line. djnz char0 ; repeat ret
As for colouring our block, we covered that in the chapter on simple attribute collision detection. The atadd routine will give us the address of an attribute cell at character cell (b, c).
Lastly, we need to decide which block to display at each cell position. Say we need 3 types of block for our game – we might use block type 0 for a space, 1 for a wall and 2 for a key. We would arrange the graphics and attributes for each block in separate tables in the same order:
blocks equ $ ; block 0 = space character. defb 0,0,0,0,0,0,0,0 ; block 1 = wall. defb 1,1,1,255,16,16,16,255 ; block 2 = key. defb 6,9,9,14,16,32,80,32 attrs equ $ ; block 0 = space. defb 71 ; block 1 = wall. defb 22 ; block 2 = key. defb 70
As we step through our table of up to 24 rows and 32 columns of maze blocks we load the block number into the accumulator, and call the fblock and fattr routines below to obtain the source graphic and attribute addresses.
; Find cell graphic. fblock rlca ; multiply block number by eight. rlca rlca ld e,a ; displacement to graphic address. ld d,0 ; no high byte. ld hl,blocks ; address of character blocks. add hl,de ; point to block. ret ; Find cell attribute. fattr ld e,a ; displacement to attribute address. ld d,0 ; no high byte. ld hl,attrs ; address of block attributes. add hl,de ; point to attribute. ret
Using this method means our maze data requires one byte of RAM for every character cell. For a playing area of 32 cells wide and 16 blocks high this would mean each screen occupying 512 bytes of memory. That would be fine for a 20-screen platformer like Manic Miner, but if you want a hundred screens or more you should consider using bigger blocks so that less are required for each screen. By using character cell blocks which are 16 x 16 pixels instead of 8 x 8 in our example, each screen table would require only 128 bytes meaning more could be squeezed into the Spectrum’s memory.