In this chapter of ZX Spectrum Assembly, we will paint the play area.
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.
ZX Spectrum Assembly, Space Battle
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.
ZX Spectrum Assembly, Space Battle
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 | |
ZX Spectrum Assembly, Space Battle
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.
ZX Spectrum Assembly, Space Battle
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.
ZX Spectrum Assembly, Space Battle
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.
ZX Spectrum Assembly, Space Battle
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.
ZX Spectrum Assembly, Space Battle
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.
ZX Spectrum Assembly, Space Battle
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
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.
In this chapter of ZX Spectrum Assembly, we will start drawing with UDG.
Painting UDG
The ZX Spectrum character map consists of two hundred and fifty-six values, of which we can redefine twenty-one, namely those between $90 (144) and $A4 (164) inclusive.
Create a folder called Step02 and copy var.asm from Step01.
Where are the UDG?
The value of memory address $5C7B contains the memory address where the user-defined graphics are located, so all we need to do is load the address where our graphics are defined into that memory address. Once this is done, drawing any character between $90 and $A4 with RST $10 will draw the graphics we have defined.
We create the file called const.asm and add the following:
; Memory address where user-defined graphics are loaded.
UDG: EQU $5c7b
In this constant we will have the address where we store the address where our graphics are located.
We paint our UDGs
It’s time for our first test, we’re going to paint the UDGs. Create main.asm and add the following lines:
org $5dad
Main:
ld a, $90
ld b, $15
Loop:
push af
rst $10
pop af
inc a
djnz Loop
ret
end Main
The first thing to do is to specify the address where we want to load the program, ORG $5DAD. We load the program at position $5DAD (23981) as it will be compatible with 16K models.
The next line is a label, Main, the entry point of the program.
Next we load one hundred and forty-four into A, LD A, $90, and twenty-one into B, LD B, $15, so we paint from character one hundred and forty-four to one hundred and sixty-four, for a total of twenty-one characters.
We make a loop of twenty-one iterations, starting with the loop label, Loop. We then keep the value of A, PUSH AF; the next instruction, RST $10, prints the character to which the code loaded into A belongs and modifies the register. We recover the value of A, POP AF.
We then increment A, INC A, so that it points to the next character, decrement B and jump to the loop if it has not reached zero, DJNZ Loop. Finally, we return to Basic, RET.
The last line tells PASMO to include the call to the address of the Main tag in the Basic loader.
Now it’s time to compile and see the results in the emulator.
pasmo --name Martian --tapbas main.asm martian.tap martian.log
But have we painted our graphics?
ZX Spectrum Assembly, Space Battle
We have painted the capital letters A to U because we have not indicated where our graphics are.
Next, we go to the main.asm file, and under the Main tag we add the following lines:
ld hl, udgsCommon
ld (UDG), hl
We load into HL the address where the graphics are, LD HL, udgsCommon, and load that value into the address where the location of our graphics is, LD (UDG), HL.
As both udgsCommon and UDG are not defined in the main.asm file, we need to add the includes for the const.asm and var.asm files after the RET statement.
include "const.asm"
include "var.asm"
Now we can recompile the program, load it into the emulator and see our graphics on the screen.
ZX Spectrum Assembly, Space Battle
Much better, right? But we painted twenty-one graphics: the ship, the shot, the explosion, the frame, the blank character, the graphics of enemy one and part of the graphics of enemy two. How are we going to paint the other two graphics of enemy two and the rest?
We load the enemies’ UDGs
Looking at the graphics definition, the first tag is called udgsCommon, and this should give us a clue as to how we’re going to do this. We have defined fifteen common UDGs (ship, shot, explosion, frame and target), so we are going to define thirty-two bytes to be able to dump the enemy graphics into them; we do this because the enemies are one per level, and the dump is only done once, just at the level change.
In the var.asm file, above udgsEnemiesLeve1, let’s add the following lines:
udgsExtension:
db $00, $00, $00, $00, $00, $00, $00, $00 ; $9f Left/Up
db $00, $00, $00, $00, $00, $00, $00, $00 ; $a0 Right/Up
db $00, $00, $00, $00, $00, $00, $00, $00 ; $a1 Left/Down
db $00, $00, $00, $00, $00, $00, $00, $00 ; $a2 Right/Down
In this block of memory we are going to dump the graphics of the enemies, depending on the level we are in.
Try compiling now and see how it looks. The enemy graphics are gone, aren’t they? It’s the udgsExtension painting.
We create the file graph.asm and implement in it the routine that loads into udgsExtension the graphics of the enemies of each of the levels, data that it receives in A.
To calculate the address where the graphics are, we multiply the level by thirty-two (bytes occupied by the graphics) and add the result to the address where the graphics of the first enemy are.
LoadUdgsEnemies:
dec a
ld h, $00
ld l, a
Since the levels range from one to thirty, we decrement A, DEC A, so that it does not add one level too many (level one adds zero to udgsEnemies, level two adds one, etc.).
The next step is to load the level into HL, for which we load zero into H, LD H, $00, and the level into L, LD L, A.
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
We multiply the level by thirty-two by adding HL to itself five times, ADD HL, HL. The first addition is like multiplying by two, the second by four, by eight, by sixteen and by thirty-two.
ld de, udgsEnemiesLevel1
add hl, de
ld de, udgsExtension
ld bc, $20
ldir
ret
We load the address of the first enemy graphic into DE, LD DE, udgsEnemiesLevel1, and add it to HL, ADD HL, DE. We load the extension address DE, LD DE, udgsExtension, load the number of bytes we are going to load into udgsExtension, LD BC, $20, and load the thirty-two bytes of the level’s enemy graphics into udgsExtension, LDIR. Finally we exit, RET.
The final aspect of the routine is as follows:
; -------------------------------------------------------------------
; Load user-defined graphics relating to enemies
;
; Entry: A -> Level from 1 to 30
;
; Alters the value of the A, BC, DE and HL registers.
; -------------------------------------------------------------------
LoadUdgsEnemies:
dec a ; A = A - 1 which does not add one level more
ld h, $00
ld l, a ; HL = level
add hl, hl ; HL = HL * 2
add hl, hl ; * 4
add hl, hl ; * 8
add hl, hl ; * 16
add hl, hl ; * 32
ld de, udgsEnemiesLevel1 ; DE = address enemy graphics 1
add hl, de ; HL = HL + DE
ld de, udgsExtension ; DE = extension address
ld bc, $20 ; BC = bytes to copy, 32
ldir ; Copies enemy bytes to extension
ret
Let’s test the new routine by editing the main.asm file, starting by changing the LD B, $15 instruction, above the Loop label, and leaving it as follows; it prints the first fifteen UDGs, the common ones:
ld b, $0f
The rest is implemented between the DJNZ Loop instruction and the RET instruction.
ld a, $01
ld b, $1e
We load in A the first level, LD A, $01, in B the total number of levels (thirty), LD B, $1E, and implement a loop that draws the enemies of the thirty levels.
Loop2:
push af
push bc
call LoadUdgsEnemies
We keep AF, PUSH AF, and BC, PUSH BC, as we use A and B to control the enemies we paint and the loop iterations. Next, we call the routine that loads the level’s enemy graphics into udgsExtension, CALL LoadUdgsEnemies.
ld a, $9f
rst $10
ld a, $a0
rst $10
ld a, $a1
rst $10
ld a, $a2
rst $10
The characters corresponding to the enemy graphics are $9F, $A0, $A1 and $A2; we load them into A, LD A, $9F, and paint, RST $10. We repeat the process with $A0, $A1 and $A2.
pop bc
pop af
inc a
djnz Loop2
Recover BC, POP BC, AF, POP AF, increment A to go to the next level, INC A, and repeat until B is zero, DJNZ Loop2.
Finally, at the end of the file and before END Main, we insert the file graph.asm.
include "graph.asm"
The final main.asm code is as follows:
org $5dad
Main:
ld hl, udgsCommon
ld (UDG), hl
ld a, $90
ld b, $0f
Loop:
push af
rst $10
pop af
inc a
djnz Loop
ld a, $01
ld b, $1e
Loop2:
push af
push bc
call LoadUdgsEnemies
ld a, $9f
rst $10
ld a, $a0
rst $10
ld a, $a1
rst $10
ld a, $a2
rst $10
pop bc
pop af
inc a
djnz Loop2
ret
include "const.asm"
include "graph.asm"
include "var.asm"
end Main
We compile, load into the emulator and see that we have painted all our graphics.
ZX Spectrum Assembly, Space Battle
ZX Spectrum Assembly, Space Battle
At this point we have defined all the graphics and learned how to paint them.
In the next chapter of ZX Spectrum Assembly, we will paint the play area.
Download the source code from here.
In this chapter of ZX Spectrum Assembly, we will define all the graphics used in Space Battle.
Definition of graphics
We will learn how to do the hexadecimal/binary conversion on the fly by doing our first exercise.
You can use ZX Paintbrush, or you can use the templates I created for GIMP, which you can download from these addresses:
The templates I have prepared are as follows:
- 8x8Template: for creating 8×8 pixel graphics.
- 256x192Template: the size of the ZX Spectrum’s screen.
For Space Battle we will use 8×8 templates. I will show you the image of the graphics, the hexadecimal codes, and your job will be to convert the codes into pixels, head first, to draw the graphics on the templates.
Hexadecimal/binary conversion
Although at first it may seem complicated to convert a hexadecimal number to binary, and vice versa, it is very simple and practically straightforward. We need to know the value of each bit (pixel) in blocks of four, which gives us a value between 0 and F, a value that can be represented by each hexadecimal digit.
In a byte, each bit to one has the following values:
Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
ZX Spectrum Assembly, Space Battle
When we convert from hexadecimal to binary, we divide the byte into two blocks of four bits (nibbles), resulting in a range of values between 0 and F (8 + 4 + 2 + 1 = 15 = F). To convert from binary to hexadecimal, we add the value of the bits to one of each nibble, which gives us the value in hexadecimal.
Suppose we have the following binary value:
0101 1001
If we add up the values of the nibbles, the result would be:
0 + 4 + 0 + 1 = 5 8 + 0 + 0 + 1 = 9
The result is that 01011001 is 59 in hexadecimal.
In hexadecimal a byte is represented by two digits. What if the value of some of the nibbles is greater than nine?
11011011 = 8 + 4 + 0 + 1 = 13 y 8 + 0 + 2 + 1 = 11
How do we represent thirteen and eleven with only one digit each? In hexadecimal, values from ten to fifteen are represented by letters.
Decimal | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
Hexadecimal | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
ZX Spectrum Assembly, Space Battle
In the example above, the hexadecimal value of 11011011 is DB.
Practising hexadecimal/binary conversion
A good way to learn is by doing, and this is what I propose to do below. Let’s look at the definition of all the UDGs we are going to use (the hexadecimal values) and draw them by doing the conversion from hexadecimal to binary.
If we work with nibbles (4 bits), the conversion table for a byte would be as follows:
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
ZX Spectrum Assembly, Space Battle
We will create a folder called Step01 and inside it a file called var.asm. Having this table at hand, we are going to draw the ship, whose definition in hexadecimal (which we are going to copy into the created file) is the following:
udgsCommon:
db $24, $42, $99, $bd, $ff, $18, $24, $5a ; $90 Ship
We convert to binary.
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
$24 | | | X | | | X | | |
$42 | | X | | | | | X | |
$99 | X | | | X | X | | | X |
$bd | X | | X | X | X | X | | X |
$ff | X | X | X | X | X | X | X | X |
$18 | | | | X | X | | | |
$24 | | | X | | | X | | |
$5a | | X | | X | X | | X | |
ZX Spectrum Assembly, Space Battle
If you transfer this conversion to ZX Paintbrush, or to the templates, the result should look like this:
ZX-Spectrum Assembly, Space Battle
We are going to continue practising, doing the conversions for the shot and the animation of the explosion of the ship. It is important that you try to translate it from hexadecimal to binary and from there to the graphics.
db $00, $18, $24, $5a, $5a, $24, $18, $00 ; $91 Shot
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
$00 | | | | | | | | |
$18 | | | | X | X | | | |
$24 | | | X | | | X | | |
$5a | | X | | X | X | | X | |
$5a | | X | | X | X | | X | |
$24 | | | X | | | X | | |
$18 | | | | X | X | | | |
$00 | | | | | | | | |
ZX-Spectrum Assembly, Space Battle
ZX-Spectrum Assembly, Space Battle
db $00, $00, $00, $00, $24, $5a, $24, $18 ; $92 Blast 1
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
$00 | | | | | | | | |
$00 | | | | | | | | |
$00 | | | | | | | | |
$00 | | | | | | | | |
$24 | | | X | | | X | | |
$5a | | X | | X | X | | X | |
$24 | | | X | | | X | | |
$18 | | | | X | X | | | |
ZX-Spectrum Assembly, Space Battle
db $00, $00, $00, $14, $2a, $34, $24, $18 ; $93 Blast 2
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
$00 | | | | | | | | |
$00 | | | | | | | | |
$00 | | | | | | | | |
$14 | | | | X | | X | | |
$2a | | | X | | X | | X | |
$34 | | | X | X | | X | | |
$24 | | | X | | | X | | |
$18 | | | | X | X | | | |
ZX-Spectrum Assembly, Space Battle
db $00, $00, $0c, $12, $2a, $56, $64, $18 ; $94 Blast 3
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
$00 | | | | | | | | |
$00 | | | | | | | | |
$0c | | | | | X | X | | |
$12 | | | | X | | | X | |
$2a | | | X | | X | | X | |
$56 | | X | | X | | X | X | |
$64 | | X | X | | | X | | |
$18 | | | | X | X | | | |
ZX-Spectrum Assembly, Space Battle
db $20, $51, $92, $d5, $a9, $72, $2c, $18 ; $95 Blast 4
Byte | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Value | 8 | 4 | 2 | 1 | 8 | 4 | 2 | 1 |
$20 | | | X | | | | | |
$51 | | X | | X | | | | X |
$92 | X | | | X | | | X | |
$d5 | X | X | | X | | X | | X |
$a9 | X | | X | | X | | | X |
$72 | | X | X | X | | | X | |
$2c | | | X | | X | X | | |
$18 | | | | X | X | | | |
ZX-Spectrum Assembly, Space Battle
ZX-Spectrum Assembly, Space Battle
From here on, I will just give the hexadecimal definition and the image of the final appearance of each UDG.
If you are wondering what the number in the comments of each definition means, it is the code of the character we are redefining, i.e. the character code we are going to send to the printer to make the graphic. Don’t worry if you don’t understand it now, it will be much clearer later.
db $3f, $6a, $ff, $b8, $f3, $a7, $ef, $ae ; $96 Top/Left
db $ff, $aa, $ff, $00, $ff, $ff, $00, $00 ; $97 Top
db $fc, $ae, $fb, $1f, $cd, $e7, $f5, $77 ; $98 Top/Right
db $ec, $ac, $ec, $ac, $ec, $ac, $ec, $ac ; $99 Left
db $35, $37, $35, $37, $35, $37, $35, $37 ; $9a Right
db $ee, $af, $e7, $b3, $f8, $df, $75, $3f ; $9b Bottom/Left
db $00, $00, $ff, $ff, $00, $ff, $55, $ff ; $9c Bottom
db $75, $f7, $e5, $cf, $1d, $ff, $56, $fc ; $9d Bottom/Right
db $00, $00, $00, $00, $00, $00, $00, $00 ; $9e White
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel1:
db $8c, $42, $2d, $1d, $b4, $be, $46, $30 ; $9f Left/Up
db $31, $42, $b4, $b8, $2d, $7d, $62, $0c ; $a0 Right/Up
db $30, $46, $be, $b4, $1d, $2d, $42, $8c ; $a1 Left/Down
db $0c, $62, $7d, $2d, $b8, $b4, $42, $31 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel2:
db $c0, $fb, $69, $5d, $7b, $14, $4a, $79 ; $9f Left/Up
db $03, $df, $96, $ba, $de, $28, $52, $9e ; $a0 Right/Up
db $79, $4a, $14, $7b, $5d, $69, $fb, $c0 ; $a1 Left/Down
db $9e, $52, $28, $de, $ba, $96, $df, $03 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel3:
db $fc, $84, $b4, $af, $99, $f7, $14, $1c ; $9f Left/Up
db $3f, $21, $2d, $f5, $99, $ef, $28, $38 ; $a0 Right/Up
db $1c, $14, $f7, $99, $af, $b4, $84, $fc ; $a1 Left/Down
db $38, $28, $ef, $99, $f5, $2d, $21, $3f ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel4:
db $f2, $95, $98, $fe, $39, $55, $92, $4d ; $9f Left/Up
db $4f, $a9, $19, $7f, $9c, $aa, $49, $b2 ; $a0 Right/Up
db $4d, $92, $55, $39, $fe, $98, $95, $f2 ; $a1 Left/Down
db $b2, $49, $aa, $9c, $7f, $19, $a9, $4f ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel5:
db $76, $99, $a4, $d4, $47, $bd, $8a, $4c ; $9f Left/Up
db $6e, $99, $25, $2b, $e2, $bd, $51, $32 ; $a0 Right/Up
db $4c, $8a, $bd, $47, $d4, $a4, $99, $76 ; $a1 Left/Down
db $32, $51, $bd, $e2, $2b, $25, $99, $6e ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel6:
db $98, $66, $59, $aa, $b6, $49, $5a, $24 ; $9f Left/Up
db $19, $66, $9a, $55, $6d, $92, $5a, $24 ; $a0 Right/Up
db $24, $5a, $49, $b6, $aa, $59, $66, $98 ; $a1 Left/Down
db $24, $5a, $92, $6d, $55, $9a, $66, $19 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel7:
db $04, $72, $5d, $74, $2e, $be, $4c, $20 ; $9f Left/Up
db $20, $4e, $ba, $2e, $74, $7d, $32, $04 ; $a0 Right/Up
db $20, $4c, $be, $2e, $74, $5d, $72, $04 ; $a1 Left/Down
db $04, $32, $7d, $74, $2e, $ba, $4e, $20 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel8:
db $00, $7c, $5a, $68, $7c, $4f, $26, $04 ; $9f Left/Up
db $00, $3e, $5a, $16, $3e, $f2, $64, $20 ; $a0 Right/Up
db $04, $26, $4f, $7c, $68, $5a, $7c, $00 ; $a1 Left/Down
db $20, $64, $f2, $3e, $16, $5a, $3e, $00 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel9:
db $e0, $d8, $b6, $6e, $5b, $36, $3c, $08 ; $9f Left/Up
db $07, $1b, $6d, $76, $da, $6c, $3c, $10 ; $a0 Right/Up
db $08, $3c, $36, $5b, $6e, $b6, $d8, $e0 ; $a1 Left/Down
db $10, $3c, $6c, $da, $76, $6d, $1b, $07 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel10:
db $e0, $ce, $bf, $3c, $73, $75, $6a, $2c ; $9f Left/Up
db $07, $73, $fd, $3c, $ce, $ae, $56, $34 ; $a0 Right/Up
db $2c, $6a, $75, $73, $3c, $bf, $ce, $e0 ; $a1 Left/Down
db $34, $56, $ae, $ce, $3c, $fd, $73, $07 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel11:
db $e0, $de, $bf, $7c, $7b, $75, $6a, $2c ; $9f Left/Up
db $07, $7b, $fd, $3e, $de, $ae, $56, $34 ; $a0 Right/Up
db $2c, $6a, $75, $7b, $7c, $bf, $de, $e0 ; $a1 Left/Down
db $34, $56, $ae, $de, $3e, $fd, $7b, $07 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel12:
db $e0, $fe, $f7, $6c, $5f, $7e, $6c, $28 ; $9f Left/Up
db $07, $7f, $ef, $36, $fa, $7e, $36, $14 ; $a0 Right/Up
db $28, $6c, $7e, $5f, $6c, $f7, $fe, $e0 ; $a1 Left/Down
db $14, $36, $7e, $fa, $36, $ef, $7f, $07 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel13:
db $07, $6c, $7e, $34, $6f, $fb, $ae, $8c ; $9f Left/Up
db $e0, $36, $7e, $2c, $f6, $df, $75, $31 ; $a0 Right/Up
db $8c, $ae, $fb, $6f, $34, $7e, $6c, $07 ; $a1 Left/Down
db $31, $75, $df, $f6, $2c, $7e, $36, $e0 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel14:
db $21, $1a, $96, $75, $4c, $3c, $62, $90 ; $9f Left/Up
db $84, $58, $69, $ae, $32, $3c, $46, $09 ; $a0 Right/Up
db $90, $62, $3c, $4c, $75, $96, $1a, $21 ; $a1 Left/Down
db $09, $46, $3c, $32, $ae, $69, $58, $84 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel15:
db $04, $02, $0d, $14, $28, $b0, $40, $20 ; $9f Left/Up
db $20, $40, $b0, $28, $14, $0d, $02, $04 ; $a0 Right/Up
db $20, $40, $b0, $28, $14, $0d, $02, $04 ; $a1 Left/Down
db $04, $02, $0d, $14, $28, $b0, $40, $20 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel16:
db $30, $48, $be, $b9, $7c, $2e, $27, $13 ; $9f Left/Up
db $0c, $12, $7d, $9d, $3e, $74, $e4, $c8 ; $a0 Right/Up
db $13, $27, $2e, $7c, $b9, $be, $48, $30 ; $a1 Left/Down
db $c8, $e4, $74, $3e, $9d, $7d, $12, $0c ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel17:
db $c0, $df, $36, $7c, $58, $77, $66, $44 ; $9f Left/Up
db $03, $fb, $6c, $3e, $1a, $ee, $66, $22 ; $a0 Right/Up
db $44, $66, $77, $58, $7c, $36, $df, $c0 ; $a1 Left/Down
db $22, $66, $ee, $1a, $3e, $6c, $fb, $03 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel18:
db $02, $71, $69, $57, $2f, $1e, $9e, $78 ; $9f Left/Up
db $40, $8e, $96, $ea, $f4, $78, $79, $1e ; $a0 Right/Up
db $78, $9e, $1e, $2f, $57, $69, $71, $02 ; $a1 Left/Down
db $1e, $79, $78, $f4, $ea, $96, $8e, $40 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel19:
db $20, $7f, $e6, $4e, $5e, $79, $78, $44 ; $9f Left/Up
db $04, $fe, $67, $72, $7a, $9e, $1e, $22 ; $a0 Right/Up
db $44, $78, $79, $5e, $4e, $e6, $7f, $20 ; $a1 Left/Down
db $22, $1e, $9e, $7a, $72, $67, $fe, $02 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel20:
db $36, $2f, $db, $be, $7c, $db, $f6, $64 ; $9f Left/Up
db $6c, $f4, $db, $7d, $3e, $db, $6f, $26 ; $a0 Right/Up
db $64, $f6, $db, $7c, $be, $db, $2f, $36 ; $a1 Left/Down
db $26, $6f, $db, $3e, $7d, $db, $f4, $6c ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel21:
db $00, $70, $6e, $54, $2b, $34, $28, $08 ; $9f Left/Up
db $00, $0e, $76, $2a, $d4, $2c, $14, $10 ; $a0 Right/Up
db $08, $28, $34, $2b, $54, $6e, $70, $00 ; $a1 Left/Down
db $10, $14, $2c, $d4, $2a, $76, $0e, $00 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel22:
db $00, $78, $6e, $56, $6d, $3b, $34, $0c ; $9f Left/Up
db $00, $1e, $76, $6a, $b6, $dc, $2c, $30 ; $a0 Right/Up
db $0c, $34, $3b, $6d, $56, $6e, $78, $00 ; $a1 Left/Down
db $30, $2c, $dc, $b6, $6a, $76, $1e, $00 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel23:
db $0c, $02, $3d, $35, $ac, $b8, $40, $30 ; $9f Left/Up
db $30, $40, $bc, $ac, $35, $1d, $02, $0c ; $a0 Right/Up
db $30, $40, $b8, $ac, $35, $3d, $02, $0c ; $a1 Left/Down
db $0c, $02, $1d, $35, $ac, $bc, $40, $30 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel24:
db $00, $77, $6e, $56, $2a, $74, $7b, $42 ; $9f Left/Up
db $00, $ee, $76, $6a, $54, $2e, $de, $42 ; $a0 Right/Up
db $42, $7b, $74, $2a, $56, $6e, $77, $00 ; $a1 Left/Down
db $42, $de, $2e, $54, $6a, $76, $ee, $00 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel25:
db $c0, $ff, $76, $6c, $5f, $7e, $6c, $48 ; $9f Left/Up
db $03, $ff, $6e, $36, $fa, $7e, $36, $12 ; $a0 Right/Up
db $48, $6c, $7e, $5f, $6c, $76, $ff, $c0 ; $a1 Left/Down
db $12, $36, $7e, $fa, $36, $6e, $ff, $03 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel26:
db $3c, $7e, $f7, $e8, $da, $e1, $68, $24 ; $9f Left/Up
db $3c, $7e, $ef, $17, $5b, $87, $16, $24 ; $a0 Right/Up
db $24, $68, $e1, $da, $e8, $f7, $7e, $3c ; $a1 Left/Down
db $24, $16, $87, $5b, $17, $ef, $7e, $3c ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel27:
db $04, $02, $39, $2d, $3f, $9e, $4c, $38 ; $9f Left/Up
db $20, $40, $9c, $b4, $fc, $79, $32, $1c ; $a0 Right/Up
db $38, $4c, $9e, $3f, $2d, $39, $02, $04 ; $a1 Left/Down
db $1c, $32, $79, $fc, $b4, $9c, $40, $20 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel28:
db $00, $37, $69, $5c, $34, $5f, $46, $64 ; $9f Left/Up
db $00, $ec, $96, $3a, $2c, $fa, $62, $26 ; $a0 Right/Up
db $64, $46, $5f, $34, $5c, $69, $37, $00 ; $a1 Left/Down
db $26, $62, $fa, $2c, $3a, $96, $ec, $00 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel29:
db $00, $37, $6d, $5e, $34, $7f, $56, $64 ; $9f Left/Up
db $00, $ec, $b6, $7a, $2c, $fe, $6a, $26 ; $a0 Right/Up
db $64, $56, $7f, $34, $5e, $6d, $37, $00 ; $a1 Left/Down
db $26, $6a, $fe, $2c, $7a, $b6, $ec, $00 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
udgsEnemiesLevel30:
db $e0, $ff, $ed, $5b, $7e, $6e, $5f, $72 ; $9f Left/Up
db $07, $ff, $b7, $da, $7e, $76, $fa, $4e ; $a0 Right/Up
db $72, $5f, $6e, $7e, $5b, $ed, $ff, $e0 ; $a1 Left/Down
db $4e, $fa, $76, $7e, $da, $b7, $ff, $07 ; $a2 Right/Down
ZX-Spectrum Assembly, Space Battle
With this we have already defined the graphics we are going to use: the ship, the shot, the explosion, the frame of the screen and the enemies.
You may notice that all the enemies have the same character code, this is because there is a limited number of characters that can be used as UDGs. Don’t worry, we’ll see a way around this later on.
ZX-Spectrum Assembly, Space Battle
It’s really very important to practice the hexadecimal to binary conversion, so don’t leave it for another day, it’s a very simple exercise.
In the next chapter of ZX Spectrum Assembly, we will start drawing with UDG.
Download the source code from here.