ZX Spectrum Assembly, Space Battle – 0x03 Play area
In this chapter of ZX Spectrum Assembly, we will paint the play area.
Translation by Felipe Monge Corbalán
Table of contents
- Play area
- Changing the active display
- We paint text strings
- We paint the game screen
- We clean and colour the screen
- We paint the information of the game
- ZX Spectrum Assembly, Space Battle
- Useful links
Play area
Create the folder Step03 and copy the files const.asm, graph.asm, main.asm and var.asm from the folder Step02.
Before starting to draw the game area, it is necessary to know that the ZX Spectrum screen is divided into two zones, the upper one with twenty-two lines (from zero to twenty-one) and the lower one (the line where the commands are entered).
If you load the program resulting from the previous chapter, when it is executed, pressing the ENTER key will display the basic list of the loader. Executing line 40 should paint the graphics, but this does not seem to be the case. They do draw, but they do so on the command line; look and you will see them draw and then disappear.
After running our program, the active part of the screen is the command line, so we need a mechanism to activate the part of the screen where we want to paint.
Changing the active display
The upper part of the screen is two, the lower part one. There is a routine in the ROM that activates one or the other channel depending on the value in register A.
Open the file const.asm and add the following lines:
; -------------------------------------------------------------------
; ROM routine that opens the display channel.
;
; Input: A -> 1 = command line
; 2 = top screen
; -------------------------------------------------------------------
OPENCHAN: EQU $1601
Next, we open the main.asm file and just below the Main tag we add the following lines:
ld a, $02
call OPENCHAN
We load the channel we want to activate into A, LD A, $02, and then call the ROM routine to activate it, CALL OPENCHAN.
We compile, load into the emulator, press any key, execute line 40 and now our graphics are painted in the right place again.
We paint text strings
When working with UDG, we could say that we are painting characters, and as such we will be painting the game screen.
We implement a routine that paints strings, giving the address where the string is and the length of the string.
We create the file print.asm and implement PrintString, which takes the address of the string in HL and the length in B. This routine changes the value of the AF, B and HL registers.
PrintString:
ld a, (hl)
rst $10
inc hl
djnz PrintString
ret
Loads the character to be drawn into A, LD A, (HL), draws it, RST $10, points HL to the next character, INC HL, and repeats the operation until B is 0, DJNZ PrintString. Finally, it exits, RET.
The final aspect of the routine, once commented, is as follows:
; -------------------------------------------------------------------
; Paints chains.
;
; Input: HL = first memory location of the string.
; B = length of the chain.
; Alters the value of the AF, B and HL registers.
; -------------------------------------------------------------------
PrintString:
ld a, (hl) ; A = character to be painted
rst $10 ; Paint the character
inc hl ; HL = next character
djnz PrintString ; Until B has a value of 0
ret
The next step is testing, so let’s edit the main.asm file. The first thing to do, so we don’t forget, is to include the print.asm file before END Main:
include "print.asm"
Just before the includes, we are going to define a string with two tags, the string itself and a second tag to mark the end of the string.
String:
db 'Hello World'
String_End:
db $
Just before the RET that takes us out of Basic, we add the call to the new routine.
ld hl, String
ld b, String_End - String
call PrintString
We compile, load in the emulator and see the results.
As you can see, the string Hello World has been painted after the graphics.
We add codes before the string and leave it as it is:
db $16, $0a, $0a, 'Hello World'
We compile, load in the emulator and see the result.
As we can see, the Hello World string now appears more centred, which we have achieved by adding characters in front of the string, specifically $16, which is the control character of the Basic AT command, and the Y and X coordinates.
Below is a list of the control characters we can use when painting strings in this way, and the parameters to send.
Character | Code | Parameters | Values |
DELETE | $0c | ||
ENTER | $0d | ||
INK | $10 | Colour | From $00 to $07 |
PAPER | $11 | Colour | From $00 to $07 |
FLASH | $12 | No/Yes | From $00 to $01 |
BRIGHT | $13 | No/Yes | From $00 to $01 |
INVERSE | $14 | No/Yes | From $00 to $01 |
OVER | $15 | No/Yes | From $00 to $01 |
AT | $16 | Y and X coordinates | Y = from $00 to $15 X = from $00 to $1f |
TAB | $17 | Number of tabulations |
Control codes must be accompanied by their parameters to avoid unwanted results. If a string is printed after TAB, a space must be added as the first character of the string.
As an exercise, try different combinations of control codes, try colour, blinking, etc.
We paint the game screen
The game screen is surrounded by a frame, but before we paint anything, let’s clean up main.asm, removing everything that’s left over; we delete from the two lines before the Loop tag to the line before the RET statement, we also delete the definition of String and String_End.
main.asm should now look like this:
org $5dad
Main:
ld a, $02
call OPENCHAN
ld hl, udgsCommon
ld (UDG), hl
ret
include "const.asm"
include "graph.asm"
include "print.asm"
include "var.asm"
end Main
Now we define the strings to paint the frame in var.asm and include them before udgsCommon.
; -------------------------------------------------------------------
; Display frame
; -------------------------------------------------------------------
frameTopGraph:
db $16, $00, $00, $10, $01
db $96, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97
db $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97
db $97, $97, $97, $97, $97, $98
frameBottomGraph:
db $16, $15, $00
db $9b, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c
db $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c
db $9c, $9c, $9c, $9c, $9c, $9d
frameEnd:
In the first DB line we define the position of the top, $16, $00, $00 and the colour, $10, $01.
On the next line we define the top of the frame, first the top left corner, $96, then thirty top horizontal positions, $97, and finally the top right corner, $98; all these numbers are in the comments of the chart definition.
On the next line we define the position of the bottom, $16, $15, $00.
On the next line we define the bottom, first the lower left corner, $9b, then thirty lower horizontal positions, $9c, and finally the lower right corner, $9d.
Let’s see how it all looks. We go back to main.asm and add the following lines just above the RET:
ld hl, frameTopGraph
ld b, frameEnd - frameTopGraph
call PrintString
We compile, load in the emulator and see the results.
We have painted the top and bottom of the frame, although the frame is not complete as the sides are missing.
We will implement a routine that prints the frame and we will do this in print.asm.
PrintFrame:
ld hl, frameTopGraph
ld b, frameEnd - frameTopGraph
call PrintString
We load into HL the memory address of the top of the frame, LD HL, frameTopGraph, load into B the length by subtracting the start address of the top from the end address of the frame, LD B, frameEnd – frameTopGraph, and call the routine that prints the strings, CALL PrintString.
All that remains is to implement a loop to paint the sides.
ld b, $01
printFrame_loop:
ld a, $16
rst $10
ld a, b
rst $10
ld a, $00
rst $10
ld a, $99
rst $10
We load into B the start line of the pages, LD B, $01, we load into A the AT control character, LD A, $16, and draw it, RST $10, we load the Y coordinate, LD A, B, and draw it, RST $10, we load the zero column, LD A, $00, and draw it, RST $10, and finally we load into A the left page character, LD A, $99, and draw it, RST $10.
We do the same with the right side, since the code is practically the same, we just mark the two lines that change.
ld a, $16
rst $10
ld a, b
rst $10
ld a, $1f
rst $10
ld a, $9a
rst $10
And so we come to the last part of the routine.
inc b
ld a, b
cp $15
jr nz, printFrame_loop
ret
We point B to the next line, INC B, load it into A, LD A, B, and check if B points to line twenty-one (the line where the bottom of the frame is), CP $15, and if not, repeat the loop until it reaches line twenty-one, JR NZ, printFrame_loop. As soon as B points to line twenty-one, we quit, RET.
The final aspect of the routine is as follows:
; -------------------------------------------------------------------
; Paint the frame of the screen.
;
; Alters the value of the HL, B and AF registers.
; -------------------------------------------------------------------
PrintFrame:
ld hl, frameTopGraph ; HL = top address
ld b, frameEnd – frameTopGraph ; B = length
call PrintString ; Paints the string
ld b, $01 ; B = line 1
printFrame_loop:
ld a, $16 ; A = control character AT
rst $10 ; Paints it
ld a, b ; A = line
rst $10 ; Paints it
ld a, $00 ; A = column
rst $10 ; Paints it
ld a, $99 ; A = left lateral character
rst $10 ; Paints it
ld a, $16 ; A = control character AT
rst $10 ; Paints it
ld a, b ; A = line
rst $10 ; Paints it
ld a, $1f ; A = column
rst $10 ; Paints it
ld a, $9a ; A = right-hand side character
rst $10 ; Paints it
inc b ; B = next line
ld a, b ; A = B
cp $15 ; B = 21?
jr nz, printFrame_loop ; B != 21, continue loop
ret
We will test if it paints the whole frame. Go back to main.asm and replace these lines:
ld hl, frameTopGraph
ld b, frameEnd - frameTopGraph
call PrintString
For this one:
call PrintFrame
We compile, load in the emulator and see the result.
As you can see, we have already painted the frame of the screen, but there is still work to be done; we have not erased the screen and you can see things that should not be there.
We clean and colour the screen
Many of the Basic listings you see have a line similar to this:
BORDER 0: INK 7: PAPER 0: CLS
This line sets the screen border to black, the ink to white and the background to black. Finally, the screen is cleaned by applying the ink and background attributes.
We will use the ZX Spectrum ROM to set the ink and background, clean the screen and implement the border colour change.
We start with the part where we clean the screen by opening the const.asm file and adding the following lines:
; System variable where the permanent colour attributes are located.
ATTR_P: EQU $5c8d
; -------------------------------------------------------------------
; ROM routine that clears the screen using the value of ATTR_P
; -------------------------------------------------------------------
CLS: EQU $0daf
ATTR_P contains the permanent colour attributes in the format FBPPPIII, where F = FLASH (0/1), B = BRIGHT (0/1), PPP = PAPER (from 0 to 7) and III = INK (from 0 to 7). On the other hand, CLS clears the screen using the attributes in ATTR_P.
We go back to main.asm and add the following lines just before the PrintFrame call:
ld hl, ATTR_P
ld (hl), $07
call CLS
We load the memory address of the permanent attributes in HL, LD HL, ATTR_P; we set FLASH 0, BRIGHT 0, PAPER 0, INK 7, LD (HL), $07. Finally, we clear the screen, CALL CLS.
We compile, load into the emulator and see the results.
Now we want to change the colour of the border. We have already seen in ZX-Pong that the BEEPER routine of the ROM changes the colour of the border, so it is necessary to store the attributes in a system variable; the attributes have the same format as seen for ATTR_P.
We go back to the const.asm file and add the constant for the system variable where the border attributes are stored.
; System variable where to store the border. Also used by BEEPER.
; The command line attributes are also stored here.
BORDCR: EQU $5c48
We go back to main.asm and set the border to black after CALL CLS.
xor a
out ($fe), a
ld a, (BORDCR)
and $c7
or $07
ld (BORDCR), a
We set A to zero, XOR A, and the border to black, OUT ($FE), A. We then load the value of BORDCR into A, LD A, (BORDCR), discard the border colour and so set it to black, AND $C7. We set the colour to white, OR $07, and load the value into BORDCR, LD (BORDCR), A.
The OR $07 command is only necessary while we are in Basic, remember that in BORDCR are the colour attributes of the command line, and if we don’t change the ink to white, it will stay as it was originally, in black, the same colour we gave to the background (border).
We compile, load into the emulator and see the results.
The only thing left to do is to paint the game information area, which we will do from the command line.
We paint the information of the game
The first thing to do is to define the title line of the game information area, at the beginning of var.asm.
; -------------------------------------------------------------------
; Title of the item information
; -------------------------------------------------------------------
infoGame:
db $10, $03, $16, $00, $00
db 'Lives Points Level Enemies'
infoGame_end:
In the first line we set the colour to magenta and position the cursor at the coordinates 0,0. Next we define the titles of the information.
In print.asm we implement the routine that paints the titles of the information.
PrintInfoGame:
ld a, $01
call OPENCHAN
When painting the titles of the information on the command line, the first thing to do is to activate channel one. We load one in A, LD A, $01, and call the channel change, CALL OPENCHAN.
ld hl, infoGame
ld b, infoGame_end - infoGame
call PrintString
We load in HL the address of the title of the information, LD HL, infoGame, in B the length, LD B, infoGame_end – infoGame, and then we call to print the string, CALL PrintString.
ld a, $02
call OPENCHAN
ret
Finally, we reactivate channel two (the top screen) and exit.
The final aspect of the routine is as follows:
; -------------------------------------------------------------------
; Paint the game information headings.
; Alters the value of the A, C and HL registers.
; -------------------------------------------------------------------
PrintInfoGame:
ld a, $01 ; A = 1
call OPENCHAN ; Activates channel 1
ld hl, infoGame ; HL = address string titles
ld b, infoGame_end - infoGame ; B = length
call PrintString ; Paints titles
ld a, $02 ; A = 2
call OPENCHAN ; Activates channel 2
ret
We go back to main.asm and after CALL PrintFrame we add the call to paint the start information titles.
call PrintInfoGame
If we compile now, try it if you want, it seems that the last thing we implemented does not work, it does not paint the title information. Actually it does, but when we go back to Basic, the message 0 OK, 40:1 deletes it.
To avoid this, we’ll stay in an infinite loop; we’ll go to the RET statement that brings us back to Basic and change it to:
Main_loop:
jr Main_loop
The final main.asm code is as follows:
org $5dad
Main:
ld a, $02
call OPENCHAN
ld hl, udgsCommon
ld (UDG), hl
ld hl, ATTR_P
ld (hl), $07
call CLS
xor a
out ($fe), a
ld a, (BORDCR)
and $c7
or $07
ld (BORDCR), a
call PrintFrame
call PrintInfoGame
Main_loop:
jr Main_loop
include "const.asm"
include "graph.asm"
include "print.asm"
include "var.asm"
end Main
We compile, load the emulator and see the result.
I don’t know about you, but I don’t like the fact that the titles are so close to the frame. Since we only have two lines in the command line without scrolling, and we need to draw the data below the title line, we have only one option left to remove a line from the play area.
In var.asm we find the frameBottomGraph tag, and one line down we see the DB that positions the cursor; we are going to change this line so that the Y coordinate is twenty instead of twenty-one.
db $16, $14, $00
In print.asm we now find the printFrame_loop tag. This loop runs until B is twenty-one; we need to change this condition so that it runs until it is twenty. Two lines above the RET of this routine we have CP $15, this is the line we need to modify, leaving it as follows:
cp $14 ; B = 20?
We compile, load into the emulator and see the results. Now we have our play area.
ZX Spectrum Assembly, Space Battle
In the next chapter of ZX Spectrum Assembly, we will implement the ship, its movement and therefore the controls.
Download the source code from here.
Useful links
ZX Spectrum Assembly, Space Battle by Juan Antonio Rubio García.
Translation by Felipe Monge Corbalán.
This work is licensed to Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Any comments are always welcome.