Spectron 2084 for the ZX Spectrum Next

My blogging has tailed off sharply since January, when I really got my teeth into my first ZX Spectrum Next game, a faithful conversion of the Robotron 2084 arcade classic from 1982.

The other day I released the first demo, of the attract screen portion of the game. While this doesn’t have any playable elements, it’s been great for me to ease into Next development, as it’s allowed me to analyse the game mechanics in a lot of detail while I was getting to grips with the intricacies of the hardware.

I just didn’t seem to have enough time to think, code and write at the same, but perhaps now I’ve got my feet more firmly under the table, I can go back and cover some of the ground. Anyway, I almost forgot to post the details here—so here they are!

You can download the runnable demo from here.

Spectron 2084 is a classic golden age arcade game for the ZX Spectrum Next, with a focus on faithfulness to the arcade version, as well as control flexibility. All controls modes will be available: Single sticks, twin sticks,

Here I present a runnable teaser of the attract screen. No playability yet, but you should get a sense of how faithfulness to the arcade version the game will be.

Best viewed on the Next Issue 2A board, using NextOS distribution V.0.8B-REV.A or any later version – available from the link below. Please re-run the update procedure if you have any problems running this demo, or contact me by email. This demo isn’t supported on the ZEsarUX or CSpect emulators.

https://www.specnext.com/latestdistro/

50Hz is currently supported on both VGA and HDMI, as well as 60Hz on VGA. Full 60Hz including HDMI will be supported in the released game.

Accompanying video: https://youtu.be/FJHyNFhjDqY

Enjoy!

Robin Verhagen-Guest
SevenFFF
May 2018

https://seven-fff.com/Spectron2084/

ZalaXa 13 — You Gotta Roll With It

In the last tutorial I looked at creating a randomized starfield for the main menu. Since then I’ve been playing around with techniques to scroll it vertically, like the starfield on the Galaga arcade machine.

I found a bug in the code I wrote. Instead of using a pair of random(ish) numbers for the X and Y coordinates of each star, I was actually using the Y coordinate of the previous star for the X coordinate of the next one. This was causing unnecessary vertically-repeating patterns.

After fiddling around with this for a while, I decided to use a third randomish number to represent the star position with the character. This leaves me with some spare bits I can use for attribute colours without introducing more repeating patterns.

I also added a seed randomizer in my test routine—instead of advancing the ROM seed sequence by one every time you press a key, I completely randomized it to any address between $0000 and $3FFF. I used Patrik Rak’s Complementary Multiply With Carry pseudo-random number generator again for this.

Same deal as last time, except this time it scrolls vertically:

Most of the code is pretty similar to last time. The section that draws the stars is duplicated. In the first iteration, the part that creates the set n, a opcode now creates a res n, a instruction instead, which undraws the previous star.

37
38
39
40
41
42
; stars.asm
 
                        ld a, (hl)                      ; Read the byte represnting the star pixel,
                        or %10000111                    ;      turn it into a "res n, a" instruction ($80 10nnn111) by setting
                        and %10111111                   ;      and clearing bits,
                        ld (ResetBit), a                ; SMC> then write this into the pixel-drawing code.

In the second iteration, we keep the set n, a instruction as it was in part 12. But before we draw the star pixel, we manipulate the Y coordinate by including the ScrollStar macro:

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
; macros.asm
 
ScrollStar              macro()
                        ld a, d                         ; Retrieve the byte containing bits 0..2 of the Y coordinate.
                        and %00000111                   ; Calculate Y mod 8 (0..7).
                        cp %00000111                    ; If 7 then
                        jp z, CharScroll                ;   do a character scroll.
                        ld a, d                         ; Otherwise do
                        inc a                           ;   a pixel
                        ld d, a                         ;   scroll.
                        inc (hl)                        ; Save px-scrolled offset back to StarField.Table.
                        jp Continue
CharScroll:
                        ld a, e                         ; Retrieve the byte containing bits 3..4 of the Y coordinate.
                        add 32                          ; Increment those two bits by one
                        ld e, a                         ;   and save back the byte. This moves into the next char row.
                        dec l                           ; Now retrieve the byte
                        ld (hl), a                      ;   containing bits 0..2 of the Y coordinate,
                        inc l                           ;   and
                        xor a                           ;   reset it to zero, ensuring we start at the top of that char row.
                        ld d, a                         ; Save that back to the byte, and also
                        ld (hl), a                      ;   save the px-scrolled offset back to StarField.Table.
Continue:
mend

Doing this as a macro isn’t really necessary at all, but when reading the code, it does emphasise that the pixel-setting half is almost the same as the pixel-clearing half. It achieves this by abstracting the extra code into a different place—without any of the call/ret/jp overhead a separate routine would entail.

The stuff in the macro decides whether we’re scrolling within a character boundary, or scrolling between character boundaries. Then it sets the appropriate bits of the Y coordinate accordingly. The specific maths here is needed because of how the Y coordinate is distributed within the screen address:

Image from the L Break Into Program blog, with thanks to Dean Belfield.

In the Spectrum’s distinctive display file, the bits labelled Y0, Y1 and Y2 govern the position (0..7) within a character row. We set these bits in the first half of the macro.

The bits labelled Y3, Y4 and Y5 govern the position of the character row (0..7) within the current screen third. We set these bits in the second half of the macro.

The bits labelled Y6 and Y7 govern which screen third the character row appears in. Because we’re repeating the same stars in all three thirds, we set these bits in the SetupStars routine:

84
85
86
87
88
89
90
; stars.asm
 
                        ld b, 8                         ; Fast way of doing ld bc, $0800 (size of a screen third).
                        add hl, bc                      ; hl is now a pixel address in the middle screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.
                        add hl, bc                      ; hl is now a pixel address in the bottom screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.

Actually, adding $0800 to the screen address using the add hl, bc instruction is an inefficient way of doing this. All we really need is to add 8 to h. Or even better, flip one or two bits of h. I might optimise this part another time.

I made a list of 32 of my favourite starfields using this new version of the randomizer test routine. I particularly like how it tends towards drifts of stars that look like constellations.

This is my favourite starfield (seed address $26B1), complete with the scroll effect:

Next time, I’ll explore setting attribute colours to make the stars twinkle. Cheers!

ZalaXa 12 — Towards a Scrolling Starfield

The technology in Namco’s arcade version of Galaga (1981) is from the same era as the ZX Spectrum, but is actually a little beefier. The main board has three Z80 processors, clocked at 3Mhz—each one just under the speed the Spectrum’s single processor runs! Sadly, I think that rules out being able to code an authentic arcade version of Galaga in this tutorial series. In particular, if you recall, NIRVANA+’s multicolour raster-chasing technique uses about 78% of the available processor T-states we normally have when writing Spectrum programs. That said, this should still be a fun exercise in pushing the machine to its limits 🙂

Arcade Galaga has a rather nice winking multicolour starfield scrolling down the screen during gameplay. This isn’t really necessary for the game, and won’t demonstrate any NIRVANA+ techniques, but I am quite attached to the effect, so I thought I’d take a short detour and try and get this working on the start menu.

I did some rough calculations, and decided we need about 100 stars, give or take. My first thought was to write a short BASIC program to prototype this, using RND and PLOT in a loop, but I decided to do it in machine code, which I find a bit easier.

My first attempt used Patrick Rak’s 8-bit Complementary-Multiply-With-Carry (CMWC) random number generator, to generate a table of random bytes. This proved the concept nicely, but then I remembered people often use the ZX Spectrum ROM as a source of weakly-random numbers. Surely this would be good enough for my purposes, as it’s only a few stars. The trick would be to pick a short sequence of numbers from somewhere in the ROM that looks natural enough. We have 16KB to play with—more on the later models, but let’s try to limit ourselves to something that will work and give the same results on all Spectrum models.

Thinking further, I realised the vertical scrolling will be the trickiest part of the problem. The Spectrum screen is laid out in vertical thirds, and having the pixels jump between third-boundaries will complicate things. For our purposes, though, we should be able to duplicate the same stars across all three. If each third wraps around, then individual stars will seem to scroll continuously into the next third at the same time as wrapping round. Einfach genial!!

Better still, we might be able to mitigate any obvious repetitive patterns, by using different attribute colours for each third. The entire starfield will appear to be shimmering because of the colour effects, and that should provide some distraction.

I came up with a routine that does this, noting the “seed” ROM address on the debug screen for reference. It waits till you press a key, then adds one byte to the seed address and does it all over again. Like this:

Excellent! There’s a bit of flicker in the middle third, but that’s just a side effect of the way I coded the keypress and screen re-clear loop.

It turns out that most of the seed values are not very random at all. But, persevering, I found a few seed values that gave fairly uniform results, without any ugly double pixels or tight bunchings. This was the most promising one, $03F3:

Not perfect, but good enough.

I’ll develop this a bit further in the next tutorial, but let’s finish up by looking at my code:

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
; stars.asm
 
SetupStars              proc
                        ld a, DimWhiteBlackP            ; Custom colour for ClsAttr.
                        call ClsAttr.WithCustomColour   ; Alternate entry point without setting colour.
RandomLoop:
                        call ClsPixels                  ; Clear all the pixels.
Seed equ $+1:           ld hl, $0000                    ; Starting point of ROM "psuedo-random" table (2 bytes per star). Try $03F3!
                        zeusdatabreakpoint 1, "zeusprinthex(1, hl)", $ ; Log current value of ROM table to debug window.
                        ld a, NumberOfStars             ; Loop through this many times, once for each star.
DrawLoop:
                        ex af, af'                      ; Save number of stars (loop counter).
                        ld e, (hl)                      ; Read 2 bytes,
                        inc hl                          ;   for this star,
                        ld a, (hl)                      ;   into de.
                        and %00000111                   ; Constrain de between 0..2047
                        ld d, a                         ;   (size of top screen third).
                        ld a, e                         ; Mask out a bit number (0..7) from the X coordinate,
                        or %11000111                    ;      turn it into a "set n, a" instruction ($CB 11nnn111),
                        ld (SetBit), a                  ; SMC> then write this into the pixel-drawing code.
                        ex de, hl                       ; Sawp the reading addr into de, and the writing addr into hl.
                        ld b, high(PixelAddress)        ; c stays 0, so this is a fast way of doing ld bc, $4000.
                        add hl, bc                      ; Calculate a pixel address in the top screen third.
                        xor a                           ; Draw a single pixel star,
SetBit equ $+1:         set SMC, a                      ; <SMC  by setting that pseudo-random
                        ld (hl), a                      ;       bit (0..7) from earlier.
                        ld b, 8                         ; Fast way of doing ld bc, $0800 (size of a screen third).
                        add hl, bc                      ; hl is now a pixel address in the middle screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.
                        add hl, bc                      ; hl is now a pixel address in the bottom screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.
                        ex de, hl                       ; Swap the writing addr back into de, and reading addr into hl.
                        ex af, af'                      ; Retrieve the number of stars (loop counter),
                        dec a                           ;   decrease it,
                        jp nz, DrawLoop                 ;   and do all over again if there are any stars left.
 
                        call WaitForAnyKeyPress         ; Spin until any key is pressed
 
                        ld hl, (Seed)                   ; Increase starting point of ROM "psuedo-random" table
                        inc hl                          ;      by one byte,
                        ld (Seed), hl                   ; SMC> save it into the routine,
                        jp RandomLoop                   ;      and rerun the routine again.
 
NumberOfStars           equ 32                          ; Constant declared locally to keep it handy.
pend

Once again, apologies for the syntax highlighter I’m using. My pair of ex af, af' opcodes apparently turned a whole bunch of the midsection into one long string. Fortunately the Z80 is not fooled by such shenanigans…

Anyway, this rather dense chunk of code clears the screen at the beginning, then on line 10 sets the ROM address seed value ($0000 initially).

Line 11 is a sweet Zeus feature that can break, or print values, based on expressions you write. Here, I’m writing an expression in slot 1, and setting it to be active on line $—the next opcode, which in this case is ld a, NumberOfStars. It doesn’t matter too much where you set it—in this case, the important thing is that it’s after hl has been set, but before it changes again.

The expression itself is zeusprinthex(1, hl). The first 1 means “always evaluate the expression1“. Everything else inside the brackets is a comma-separated list of things to print—in this case, just hl. Because I used zeusprinthex, the arguments are printed as hex values—as you might expect, zeusprint(1, hl) would print the decimal value of hl.

Referring back to the video, this is indeed what happens.

The next section (lines 12-19) sets up a loop counter, stores it away in the a' alternative register, then reads a couple of bytes from the ROM. These bytes, taken together, are effectively a random number between 0 and 65535. The size of our screen thirds is 2KB, or 2048 bytes. It turns out we can zeroise the leftmost five bytes, to turn it into a random number between 0 and 2047. Once again, powers of two are the bomb!

10
11
12
13
14
15
16
17
18
19
; stars.asm
 
                        ld a, NumberOfStars             ; Loop through this many times, once for each star.
DrawLoop:
                        ex af, af'                      ; Save number of stars (loop counter).
                        ld e, (hl)                      ; Read 2 bytes,
                        inc hl                          ;   for this star,
                        ld a, (hl)                      ;   into de.
                        and %00000111                   ; Constrain de between 0..2047
                        ld d, a                         ;   (size of top screen third).

The following section (lines 20-22) Grabs three of the bits from the part of the screen address that governs the X coordinate (meaning it won’t change as it vertically scrolls), and picks one of the 8 pixels within the pixel address to plonk a star into.

18
19
20
21
22
; stars.asm
 
                        ld a, e                         ; Mask out a bit number (0..7) from the X coordinate,
                        or %11000111                    ;      turn it into a "set n, a" instruction ($CB 11nnn111),
                        ld (SetBit), a                  ; SMC> then write this into the pixel-drawing code.

This is really fancy stuff. The Z80 processor is wired up internally in a very logical and consistent way, which means there are many relationships between similar opcodes. The opcodes for setting a bit all follow this rule, or formula: SET b, r is a two-byte instruction, the first of which is always $CB. The second is $C0+(8*b)+r, where b is a bit number between 0 and 7, and r is a register chosen from one of these values:

Register    Value
A           7
B           0
C           1
D	    2
E	    3
H	    4
L	    5

We’re using register a, which is %00000111, to set the star pixel. $C0 also happens to be $11000000. Adding these two together gives us a mask of %11000111. We take the three bits we chopped out of the X coordinate, which are in the exact position to fill the three zero bits in our mask, then or them together in line 22. The result is an opcode that’s always one of the following:

set 0, a
set 1, a
set 2, a
set 3, a
set 4, a
set 5, a
set 6, a
set 7, a

Having done that, line 22 writes it into the correct place in the program (line 27), ready to be run shortly. This is similar to how an assembler works, but we used the technique in our own program.

The code between lines 23 and 35 calculates a pixel byte based on our X and Y coordinates, executes our hand-assembled set instruction, and writes the star byte to the display file. It then does it two more times to the other two screen thirds, taking advantage of the fact that the screen thirds are all separated by exactly $0800 bytes (2048 in decimal).

The rest of it is just boring glue code that waits for a key to be pressed, moves to the next ROM seed value, clears the screen again, and reruns the whole process.

In the next tutorial I’ll explore ways to vertically scroll the stars. Cheers!

ZalaXa 11 — Flashy 1UPmanship

Just a short post today. In the arcade Galaga, the 1UP text flashes while player one is playing.

We can’t use the FLASH attribute bit for this, because that would alternate between red and black backgrounds. Instead, let’s use the same technique we applied to make the ship shimmer:

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
; sprites.asm
 
                        ld a, (FRAMES)                  ; Read the LSB of the ROM frame counter (0.255)
                        ld b, a                         ; Save the value for the 1UP animation
                        and %00000011                   ; Take the lowest 2 bits (effectively FRAMES modulus 4),
                        ret nz                          ;   and return 3 out of every 4 frames.
 
                        ld a, (MovePlayer.AnimOffset)   ; For every 4th frame, read player's tile offset,
                        xor %00000100                   ;       alternate between (0 => 4 => 0 => 4 => etc),
                        ld (MovePlayer.AnimOffset), a   ; SMC>  then save it back.
 
                        ld a, b                         ; Restore the frame counter
                        and %0010000                    ; 16 out of every 32 frames will be zero,
                        jp z, OneUp                     ;   which is already the attribute value for black,
                        ld a, BrightRedBlackP           ;   otherwise bright red for the other 16 frames.
OneUp:                  ld (AttributeAddress), a        ; Set this colour
                        ld (AttributeAddress+1), a      ;   for the three 1UP
                        ld (AttributeAddress+2), a      ;   attributes.

We’re already reading the frame counter in AnimateDemo, and using it to do something every four frames. Let’s save the value (which is quicker than reading it again), then mask out everything but the fifth bit. This gives us 16 frames with the value 0, and 16 frames with the value 16.

Actually, we only hit this piece of code for one in every four frames, because the earlier code returns three out of every four frames. This doesn’t matter though, because the points where the colours change are aligned with the frames that are let through.

Zero is handily already the colour of black, so whenever it’s non-zero we’ll set it to bright red. Then we’ll set the first three attributes to whatever colour we calculated.

Simple! More next time 🙂

ZalaXa 10 — A Naïve Scoring Routine

I suppose I am vaguely putting off coding the ZalaXa aliens, so my displacement activity this evening is going to be a scoring routine 🙂

Anyone who has read Jonathan Cauldwell‘s excellent book How To Write ZX Spectrum Games will be nodding sagely during the next part! If you haven’t, I highly recommend doing so. Jon is a highly respected legend who kept the homebrew scene alive in the darkest days, and is still writing games and contributing behind the scenes to many aspects of the scene today. The book is available in a 97 page PDF on his website, and in bitesize chunks deep in the chuntey field. The scoring chapter I’m referring to is Chapter 10—it wouldn’t go amiss to take a small detour and read it before continuing.

In the code for part 10, I’ve added a variant of Jonathan’s routine. The first part (I hope) is more or less the same, except he likes to store his numbers as ASCII codes (48 to 57), whereas I prefer to store them as indices (0 to 9) because indices are more useful when working with table offsets.

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
; utilities.asm
 
UpdateScore             proc
                        ld a, (hl)                      ; Current value of digit
                        add a, b                        ; Add points to this digit
                        ld (hl), a                      ; Place new digit back in score
                        cp 10                           ; More than nine?
                        jp c, Redraw                    ; No - relax.
                        sub 10                          ; Subtract 10
                        ld (hl), a                      ; Put new digit back in score
Digit:                  dec hl                          ; Previous digit in score
                        inc (hl)                        ; Up this by one
                        ld a, (hl)                      ; What's the new value?
                        cp 10                           ; Gone past nine?
                        jp c, Redraw                    ; No, scoring done
                        sub 10                          ; Down by ten
                        ld (hl), a                      ; Put it back
                        jp Digit                        ; Go round again
Redraw:

What this code does (and feel free to trace through it with with the Zeus debugger), is take the address of the digit (0 to 5) you pass in hl, and add the number you pass in b to it (1 to 9), carrying over into the left digits as necessary.

We call it like this, from the AnimateDemo routine that checks for the M key and flashes the border red:

5
6
7
8
9
10
11
12
13
14
15
16
; sprites.asm
 
                        ld bc, zeuskeyaddr("M")         ; Get the I/O port address for M (fire)
                        in a, (c)                       ; Read keys
                        and zeuskeymask("M")            ; Mask out everything but M
                        ld a, Black                     ; Assume black border
                        jp nz, SetBorder                ;   if M was not pressed.
                        ld hl, Score.D1                 ; Otherwise
                        ld b, 1                         ;   add 10 to
                        call UpdateScore                ;   score,
                        ld a, Red                       ;   then assume red border.
SetBorder:

The key parts here are lines 12 to 14, which adds 1 to the the second digit1—in other words, it adds 10 to the score.

The second half of UpdateScore involves drawing the score on the screen, and looks like this:

75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
; utilities.asm
 
Redraw:
                        ld de, $1000                    ; $1000 is 0, 16 in NIRVANA+ coordinates
                        ld b, 6                         ; Process 6 digits,
                        ld hl, Score.D5                 ;   starting with the leftmost digit.
RedrawLoop:
                        ld a, (hl)                      ; Read value of score digit (0..9, not ASCII)
                        or a                            ; Fast way to compare with zero
SkipSMC equ $+1:        jr z, SkipDraw                  ; <SMC If leading zero, skip this digit
                        push hl                         ; Otherwise, save digit address
                        call NIRVANA_printC             ; Print character (a=index, d=pxline, e=column)
                        pop hl                          ; Restore digit address
                        inc e                           ; Increase column
                        xor a                           ; Set the skip to never skip any zeroes
                        ld (SkipSMC), a                 ;   SMC> by changing it to jp +0
SkipDraw:
                        inc hl                          ; Set next digit for processing
                        djnz RedrawLoop                 ; Reduce the digit count and repeat until zero
                        ld a, $0A                       ; Set the skip to skip leading zeroes again
                        ld (SkipSMC), a                 ;   SMC> by changing it to jp +$0A
                        ret
pend

The main thrust of this is that it calls one of the NIRVANA+ API functions, NIRVANA_printC. This takes a character index in a, a pixel line Y coordinate in d, and a column coordinate (0 to 31) in e.

There’s a fancy piece of self-modified code that drops the leading zeroes, but retains any trailing zeroes. This is certainly not the only way to do that, but I went with this method in the moment.

The character indices refer to a NIRVANA+ table called CHAR_TABLE, which contains character data in sets of 8 bytes—the same format as the Spectrum user-defined graphics.

It’s worth mentioning that I used NIRVANA_printC instead of the FZX print routine, mostly because NIRVANA_printC is about as optimised as it gets—even though that means storing two copies of the digit graphics in memory.

I shifted these characters down one pixel from how they appear in the Namco.fzx font. I hadn’t anticipated this, but NIRVANA+ only allows you to render characters and tiles on even-numbered pixel lines, and in part 9 I had chosen to print the white scores on an odd-numbered line to give a two pixel gap between them and the red labels above.

The character data looks like this:

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
; database.asm
 
; ASM data file from a ZX-Paintbrush picture with 8 x 8 pixels (= 1 x 1 characters)
Char proc Table:
::CHAR_TABLE:
                                                            No equ 0
    ;  R0   R1   R2   R3   R4   R5   R6   R7      CharID    Notes
    db $00, $38, $4C, $C6, $C6, $C6, $64, $38    ; 00        Score digit 0
    db $00, $18, $38, $18, $18, $18, $18, $7E    ; 01        Score digit 1
    db $00, $7C, $C6, $0E, $3C, $78, $E0, $FE    ; 02        Score digit 2
    db $00, $7E, $0C, $18, $3C, $06, $C6, $7C    ; 03        Score digit 3
    db $00, $1C, $3C, $6C, $CC, $FE, $0C, $0C    ; 04        Score digit 4
    db $00, $FC, $C0, $FC, $06, $06, $C6, $7C    ; 05        Score digit 5
    db $00, $3C, $60, $C0, $FC, $C6, $C6, $7C    ; 06        Score digit 6
    db $00, $FE, $C6, $0C, $18, $30, $30, $30    ; 07        Score digit 7
    db $00, $78, $C4, $E4, $78, $9E, $86, $7C    ; 08        Score digit 8
    db $00, $7C, $C6, $C6, $7E, $06, $0C, $78    ; 09        Score digit 9
 
    Count equ ($-Table)/8
    if Count > 255
      zeuserror "Character table has grown larger than 255 entries."
    endif
 
pend

There’s a fancy way we can generate this data in db format from a SCREEN$ or .scr file using ZX-Paintbrush. I will talk about this in a future blog post.

The end result, when we hit the M key, is that the score counts up—quite fast, in full left-justified glory, and in conjunction with the border flashing red. We’ll certainly need to put some limit on the rate of fire when we come to program the bullet routines, but it’s fine for now.

I rather suspect that this routine is not optimised enough for the final game, and we will have to revisit it later—perhaps by printing a single score digit in six successive frames, or some other trickery. We will see!

ZalaXa 9 — Proportional Fonts with FZX

FZX is an excellent proportional font driver by Andrew Owen and Einar Saukas. The latest source code can be downloaded here, although the version I have added to the code for part 9 has been slightly modified.123

FZX is an indispensible font editor by Klaus Jahn. Download it from here by clicking on the image.

The SetupMenu routine has a new section, as I’ve rewritten it to use a proportional font instead of the standard Spectrum font.

7
8
9
10
11
12
;menu.asm
 
                        ld hl, Font.Namco               ; Set FZX font
                        ld (FZX_FONT), hl
                        ld hl, MenuText.FZX             ; Start of menu ASCII data
                        PrintTextHL()                   ; Macro to print FZX proportional text with FZX

We set the font file to use 4, load hl with the address of the new menu text, and invoke the PrintTextHL macro. The macro looks like this:

23
24
25
26
27
28
29
30
31
32
33
34
35
;macros.asm
 
PrintTextHL             macro()
PrintMenu:              ld a, (hl)                      ; for each character of this string...
                        cp 255
                        jp z, Next                      ; check string terminator
                        push hl                         ; preserve HL
                        call FZX_START                  ; print character
                        pop hl                          ; recover HL
                        inc hl
                        jp PrintMenu
Next:                                                   ; This will be whatever code follows the macro
mend

We don’t really need to know much about the internals of FZX_START, except that it prints a single ASCII character in the range 32 to 255. It uses a pixel coordinate system, so the numbers are eight times larger than the BASIC PRINT routine. It recognises the At constant, which is followed by a pair of coordinate bytes, Y first.

Here is the revised MenuText table. You will see I have cheated slightly—because FZX doesn’t set attribute colours, I have retained the previous ROM printing code, which sets the same attributes as before, but only prints spaces. I wouldn’t normally do it like this, but I intend to change the menu again later, so let’s keep it quick and dirty for now.

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
;macros.asm
 
MenuText                proc                            ; Named procedure to keep our print data tidy
                        db At, 7, 13                    ; These codes are the same as you would use
                        db Paper, Black, PrBright, 1    ;   with Sinclair BASIC's PRINT command
                        db Ink, Red, " "                ; Set the attributes here, with spaces,
                        db Ink, Yellow, " "             ;   because FZX only prints pixels
                        db Ink, Cyan, " "
                        db Ink, Magenta, " "
                        db Ink, White, " "
                        db Ink, Green, " "
                        db At, 21, 6
                        db Ink, Yellow, "      "
                        db Ink, White, "      "
                        db Ink, Yellow, "        "
Length                  equ $-MenuText                  ; Let Zeus do the work of calculating the length
                                                        ; ($ means the current address Zeus is assembling to)
FZX:                    db At, 56, 104                  ; FXX coordinates are (Y, X) in pixels
                        db "ZA%AXA"
                        db At, 168, 55
                        db "PRESS SPACE TO START"
                        db 255                          ; Terminator byte
pend

You may also notice the % character in ZA%AXA. I did this because L in the Namco font was a bit narrower, and stopped the text aligning to character boundaries. I defined an unused character as a slightly wider L.

The Namco font is imported with a standard import_bin directive:

16
17
18
19
20
;database.asm
 
Font                    proc
  Namco:                import_bin "..\fonts\Namco.fzx"
pend

I’ve done something very similar in SetupGame:

25
26
27
28
; menu.asm
 
                        ld hl, GameText                 ; Start of menu ASCII data
                        PrintTextHL()                   ; Macro to print FZX proportional text with FZX
45
46
47
48
49
50
51
52
53
; database.asm
 
GameText                proc                            ; FXX coordinates are (Y, X) in pixels
                        db At, 0, 0, "&UP"
                        db At, 0, 94, "HIGH SCORE"
                        db At, 9, 0, "0"
                        db At, 9, 112, "20000"
                        db 255                          ; Terminator byte
pend

The &UP text is actually 1UP. Proportional fonts conventionally have all their digits to same width, so that columns of numbers always line up. Doing it this way makes the 1 of 1UP start on the second pixel of the line, so I made a shifted version of it in another space character.

If you recall, in part 8 we set the first character line of the game screen to bright red, and the second line to bright white, using the ClsNirvana routine. Well, if we got everything right, our new score text will appear in the right place, with these colours.

It does! I think this looks reasonably like the font Namco used in the original arcade version of Galaga.

ZalaXa 8 — Clearing the NIRVANA+ Screen

If you take a screenshot of the part 7 code and look at it in a screen editor such as ZX-Paintbrush, you’ll see we’re setting attributes to black-on-black1 but not clearing pixels. This is going to cause us problems later when we start writing to the screen, so let’s address that now.

We’ll still clear the 8×8 attributes, because that happens very fast and gives a nice snappy transition. But let’s add a ClsNirvana routine that we call afterwards:

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
; utilities.asm
 
ClsNirvana              proc
                        di                              ; 1) Clear all pixels
                        ld (RestoreStack), sp           ; SMC> Save the stack
                        ld sp, AttributeAddress         ; Set stack to end of screen
                        ld de, $0000                    ; All pixels unset
                        ld b, 0                         ; Loop 256 times: 12 words * 256 = 6144 bytes
LoopPixels:
                        loop 12
                          push de
                        lend
                        djnz LoopPixels
RestoreStack equ $+1:   ld sp, SMC                      ; <SMC Restore the stack
 
                        ld hl, ClsNirvanaGame           ; 2) Clear 8x2 attributes
                        ld ix, race_raster
                        ld de, NAttrVOffset
                        xor a
                        ld (Col+1), a
LoopAttr:               ld b, (hl)
                        xor a
                        cp b
                        jp z, TopRow
                        inc hl
                        ld c, (hl)
                        inc hl
Times:                  ld a, (Col+1)
                        cp 32
                        jp nz, Skip
                        xor a
                        add ix, de
Skip:                   inc a
                        ld (Col+1), a
Col:                    ld a, (deltas-1)                ; NIRVANA_org+8957 (65280, $FF00)
                        ld (Delta+2), a
Delta:                  ld (ix+0), c
                        djnz Times
                        jp LoopAttr
TopRow:
                        ClsAttrLine(0, BrightRedBlackP) ; 3) Clear top row of 8x8 attributes
                        ret
pend

This is split into three sections[I believe Einar Saukas, the author of NIRVANA+, gave me the original version of this routine, although I’ve hacked it about several times since. Thanks, Einar![/note]:

  1. Pixel clear: This uses a faster, stack-based way of block copying bytes than we used in the ldir-based ClsAttr routine (part 3).
  2. Multicolour attribute clear: This is the fiddly one. there are 2944 ($B80) multicolour attributes, and they aren’t laid out contiguously in memory. In fact the attributes for column 7 aren’t even near the attributes for column 8, for example. Every line of attributes is laid out 82 bytes after the previous line, moving down the screen. To find anything else, we need to use the deltas lookup table that NIRVANA+ handily provides. The upshot is that we need a slightly more complicated routine to set these attributes. This section uses a colour table for more flexibility. I’ll talk about this in more detail in a minute.
  3. Top row attribute clear: In NIRVANA+, the top row of attributes are always rendered with standard 8×8 attributes. The third section does this. Here, I’m setting them to red, because the arcade Galaga displays red status text on the top row. You can configure NIRVANA+ to render some of the lower rows with 8×8 attributes too. The main advantage of doing this is to to reclaim some of the processor time used for drawing the attributes for your user code, if your program design can get away with not having multicolour over the full screen. For this reason, I’m being slightly flexible and writing this section with a macro, which allows me to specify the row I want to clear2:
21
22
23
24
25
26
27
28
29
30
31
32
33
34
; macros.asm
 
ClsAttrLine             macro(Line, Colour)
                        if Colour = DimBlackBlackP
                         xor a
                        else
                          ld a, Colour
                        endif
                        ld hl, AttributeAddress+(Line*32)
                        ld (hl), a
                        ld de, AttributeAddress+(Line*32)+1
                        ld bc, 32
                        ldir
mend

I usually set any pixels and 8×8 attributes at the end of setup routines, as they can sometimes display earlier than the multicolour parts, particularly if you have NIRVANA+ turned off while you do the setup. I don’t think it matters much here, but it’s helpful to keep this as the last section of ClsNirvana.

The colour table used by the second section looks like this. It consists of pairs of bytes, with a zero byte terminator. The first byte of each pair is a count, and the second byte is a colour. The counts should add up to 2944. By all means make them add up to less, but if they add up to more, code will get overwritten and weird things will happen. Be warned!

Here, I’m setting the second character row to white on black (32 columns x 4 attribute lines = 128), as we’ll display scores here. The rest of the screen will get cleared to yellow on black. Our bullets will probably be yellow, and this might make the drawing code slightly easier if the screen is already the right colour.

35
36
37
38
39
40
41
42
43
44
; database.asm
 
ClsNirvanaGame          proc
                        db 128, BrightWhiteBlackP
                        loop 11
                          db 255, BrightYellowBlackP
                        lend
                        db 11, BrightYellowBlackP
                        db 0
pend

The sprites (only the ship, so far) have their own background and foreground colours, and will always overlay attribute values written this way, so we don’t need to worry about them here.

I defined a whole bunch of colour constants uing equ. This lets us refer to named foreground and background colours when we hardcode them. Check them out on line 81 of constants.asm, if you’re interested.

If you assemble and run the part 8 code, you will see… exactly the same results as part 7! But we have set ourselves up nicely for the next part, so it’s all good 🙂

If you want to see at least some visible change, play around with the ClsNirvanaGame colour table. It can be as long or complicated as you like! See if you can make a blocky pattern out of individual attributes. I used this technique to animate clouds on the Jet Power Jack main menu—notice how both the blue and white clouds go behind most of the graphics, but go in front of a few parts – the letters p, e and k in the title, for example:

Yes, this really is the 406th version of Jet Power Jack, and counting3. One day it will get finished and released…

That’s all for today! Let’s get fancy with proportional fonts next time 🙂

ZalaXa 7 — Moving the Ship

Just a short post today. As I’m sure you realised, I wanted to try and make a Galaga-like game for this multicolour tutorial series.

I designed a wide ship sprite, with two variants to give a flickering animation effect. My first attempt used square btiles, but I didn’t much like how it looked:

The biggest problem with this is the graphic can only be 10 pixels wide when preshifting left and right within a 16 pixel wide tile—any more and you need wide tiles to capture the full range of shifts. This is even worse when you’re preshifting in one pixel steps—then, the graphic can only be 9 pixels wide. The ship doesn’t look too bad in ZX-Paintbrush, but it looked way too small and unimposing on the Spectrum screen.

The original arcade Galaga operated in portrait mode, so perhaps this has a bearing on how they designed the sprites.

My wide tile ship looks better, I think. With 24 pixel wide tiles, the graphic can be a maximum of 18 pixels wide when preshifting in two pixel steps1. It reminds me of the God Phoenix from Battle of the Planets/Gatchaman.

This will cause us difficulties later, and we’ll probably have to squeeze every last T-state out of the machine to put aliens and bullets on the screen. Oh well, it’ll be a challenge!

I modified the code from part 6 by removing the lines of MovePlayer that related to moving up and down. I also speeded up the animation to cycle every four frames, to give more of a dynamic flickering effect:

15
16
17
18
19
; sprites.asm
 
                        ld a, (FRAMES)                  ; Read the LSB of the ROM frame counter (0.255)
                        and %00000011                   ; Take the lowest 2 bits (effectively FRAMES modulus 4),
                        ret nz                          ;   and return 3 out of every 4 frames.

Then I added some code to check the M key and flash the border red in every frame it is pressed. This won’t happen in the real game, but it feels like a small amount of progress to make something visual happen early on.

5
6
7
8
9
10
11
12
13
14
15
; sprites.asm
 
                        ld bc, zeuskeyaddr("M")
                        in a, (c)
                        and zeuskeymask("M")
                        ld b, Black
                        jp nz, SetBorder
                        ld b, Red
SetBorder:
                        ld a, b
                        out (ULAPort), a

That’s pretty much it. I think the result looks quite nice.

More next time. Happy new year!

ZalaXa 6 — Preshifted Sprites

At the end of part 5, I promised to make our sprite move around the screen this time.

To that end, in the code for part 6, I’ve added a call to new MovePlayer routine to the main loop.

17
18
19
20
21
22
23
; main.asm
 
Loop:                                                   ;   of a game and the start of the next one
                        halt                            ; Wait until the next 50th second frame
                        call AnimateDemo                ; Animate our monster
                        call MovePlayer                 ; Move up/down/left/right
                        jp Loop                         ; Go into an endless loop (for now...)

The MovePlayer routine is fairly long. Let’s split it into two parts:

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
; sprites.asm
 
MovePlayer              proc
                        ld de, 0                        ; d (vertical) and e (horizontal) will hold -2/0/+2 movement offsets
                        ld bc, zeuskeyaddr("OP")        ; Get the I/O port address for O (left) and P (right)
                        in a, (c)                       ; Read keys
                        ld b, a                         ; Save value for Right check
                        and zeuskeymask("O")            ; Mask out everything but O
                        jp nz, Right                    ; If result is non-zero O was not pressed, so check Left key
                        ld e, -2                        ; otherwise set horizontal offset to -2
                        jp Up                           ; and skip Left key check
Right:
                        ld a, b                         ; Retrieve Left/Right keypress reading
                        and zeuskeymask("P")            ; Mask out everything but P
                        jp nz, Up                       ; If result is non-zero P was not pressed, so check Up key
                        ld e, +2                        ; otherwise set horizontal offset to +2
Up:
                        ld bc, zeuskeyaddr("Q")         ; Get the I/O port address for Q (up)
                        in a, (c)                       ; Read keys
                        and zeuskeymask("Q")            ; Mask out everything but Q
                        jp nz, Down                     ; If result is non-zero Q was not pressed, so check Down key,
                        ld d, -2                        ; otherwise set vetical offset to -2.
Down:
                        ld bc, zeuskeyaddr("A")         ; Get the I/O port address for A (down)
                        in a, (c)                       ; Read keys
                        and zeuskeymask("A")            ; Mask out everything but A
                        jp nz, Move                     ; If result is non-zero Q was not pressed, so go ahead
                        ld d, +2                        ; otherwise set vetical offset to +2
Move:

Hopefully the code is commented well enough to understand it. All you really need to get is that the QAOP keys are read, then some vertical and horizontal directional offsets are calculated, having values -2, 0 or +2 depending on which keys were pressed.

This means we will be moving around the screen in steps of two pixels. Why not steps of one pixel, you ask? Well, the nature of NIRVANA+ with its 8×2 attributes makes it harder to move vertically in one pixel steps—we could do it, but the colours of the sprite tile would probably get messed up in the odd-numbered pixel lines. This is essentially attribute clash, but on a two pixel grid instead of the classic eight pixel grid.

We can certainly move horizontally in one pixel steps, but it would create an imbalance in most games to move at half the speed horizontally than you did vertically. So, as a design decision, I’ve made both axes move in steps of two pixels.

The second half of the routine is concerned with calculating the coordinates for the player sprite (now sprite B). It also introduces a second blank sprite (sprite A).

It’s important to know that NIRVANA sprites are drawn every frame, in their last position. But they’re never undrawn—you have to do that part yourself. It’s a pretty flexible way of doing things, and opens up a few techniques for doubling or tripling the effective number of sprites you have available. In our case, though, we want to blank out the trails our sprite leaves as it moves around. In this demonstation, the simplest way to do that is with a second blanking sprite.

Remember earlier I said we have eight available “for free” as part of standard NIRVANA+)? In fact that was for square btiles; only five wtiles can be drawn “for free”. For now this doesn’t matter, as we’re still exploring concepts and options. We will look at some ways to break past this limit later. Here’s the code1:

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
; sprites.asm
 
Move:
Y equ $+1:              ld a, SMC                       ; <SMC Read the previous player vertical position
                        ld (Sprites.ALine), a           ;      and set the blank sprite vertical position to this.
                        add d                           ; Then add the vertical offset (-2/0/+2)
                        cp -2                           ; Check if we went negative
                        jp nz, MaxY                     ; If not, carry on,
                        xor a                           ;   otherwise set vertical position to 0 (the minimum).
MaxY:
                        cp 200                          ; Check if we went below the bottom of the screen
                        jp c, SetY                      ; If not, carry on,
                        ld a, 200                       ;   otherwise set vertical position to 200 (the maximum).
SetY:
                        ld (Sprites.BLine), a           ; Set the player sprite to the new verified vertical position
                        ld (Y), a                       ;   and also save it for next time (for the blank sprite
                                                        ;   to be drawn underneath the player sprite)
X equ $+1:              ld a, SMC                       ; <SMC Read the previous player horizontal position
                        call CalculatePlayerX           ; Call the routine to calculate the column and tile offset
                        ex af, af'                      ; The column is returned in a'
                        ld (Sprites.AColumn), a         ; Set column for blank sprite (based on previous X position)
 
                        ld a, b                         ; CalculatePlayerX saved the previous X position in b, restore it
                        add a, e                        ;   then add the horizontal offset (-2/0/+2)
                        cp -2                           ; Check if we went negative
                        jp nz, MaxX                     ; If not, carry on,
                        xor a                           ;   otherwise set vertical position to 0 (the minimum).
MaxX:
                        cp 238                          ; Check if we went beyond the right of the screen
                        jp c, SetX                      ; If not, carry on,
                        ld a, 238                       ;   otherwise set vertical position to 238 (the maximum).
SetX:
                        ld (X), a                       ; Save it for next time (for the blank sprite underneath)
                        call CalculatePlayerX           ; Call the routine again for the new player X position
AnimOffset equ $+1:     add a, SMC                      ; The X offset is returned in a, add the animation offset from AnimateDemo
                        ld (Sprites.BIndex), a          ; Set tile index for player sprite (current position)
                        ex af, af'                      ; The column is returned in a'
                        ld (Sprites.BColumn), a         ; Set column for player sprite (current position)
                        ret
pend

This second half of the routine does bounds checks to stop the sprite going off the screen. If we didn’t, we’d crash the program quite badly! The reason for this is that NIRVANA+’s multicolour attribute data is interspersed tightly with the code that draws the attributes. If we start going off the map, we will overwrite important code pretty soon.

This routine calls another routine called CalculatePlayerX, twice—once for the blanking sprite and once for the player sprite. I’ve done this to try and reduce duplicated code, and the calculations for both are more or less the same (except the blanking sprite always uses the same tile index). This is the routine:

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
; sprites.asm
 
CalculatePlayerX        proc                            ; X coordinate is passed in a (0.255)
                        ld b, a                         ; Save the X coordinate for later
                        and %11111000                   ; Round it to the nearest eight
                        rra                             ;   then
                        rra                             ;   divide
                        rra                             ;   by eight to get the column (0..31).
                        ex af, af'                      ; Column is returned in a'
                        ld a, b                         ; Get the X coordinate back
                        and %00000110                   ; Get the remainder after rounding it to the nearest eight
                        rra                             ;   then divide that by two to get the tile index (0/2/4/6)/
                        add a, WTile.Monster            ; Tile index is returned in a
                        ret
pend

This does something interesting. Remember that we made eight monster tiles—four for each animation cycle in part 5?

Notice that we’re storing our X coordinates in the range from 0 to 255. But NIRVANA+ actually takes a column number in the range 0 to 31 for all its sprite and tile operations. One way to look at this is like a fraction—the column is the whole number part, and the pixel position within the column (between 0 and 7) is the fractional part. Actually, this is a fraction, albeit a binary2 one!

It turns out that the bottom-most three bits of the X coordinate hold the fractional part—the maximum value three bits can hold is %111, or decimal 7, which is our maximum pixel position within the column. This means that the top-most five bits contain the column number.

In order to separate out these two numbers from the X coordinate byte, all we need are a few bitwise operations. The column number can be obtained by dividing by eight. Dividing by powers of two is easy in Z80, using one of the shift-right opcodes. We can also do it, faster, with the rotate-right opcodes—provided none of the bits being shifted out contain 1s (otherwise they’ll be shifted back in at the left hand side, giving a number larger than 31!) I’m ensuring this doesn’t happen with and %11111000, which clears the bottom-most three bits before rotating them.

Obtaining the fractional part is easier—we just have to mask the X coordinate with and %00000110, to clear all the bits apart from the bottom three3. If we had seven tiles, each shifted a single pixel, this would be perfect. But we’re moving in two pixel steps, so we need to divide this by two with rra. Notice that there isn’t any remainder4 when dividing by a power of two in Z80—we’re effectively rounding down after dividing, which is exactly what we need here!

This preshifting technique is quite common in 8-bit graphics programming. Generally, it makes a compromise between speed (all those shifted versions could be calculated on the fly, but it would be slow) and size (four or eight times the number of tiles!). R-Tape has written a more comprehensive tutorial about preshifting and sprite animation. You may find this interesting—it doesn’t relate specifically to NIRVANA, but the techniques are the same.

With the column number (0..31) and tile offset (0/2/4/6) we have everything we need to calculate the correct tile to make it look as if we’ve moving in two pixel steps. The only thing remains is to add in the animation cycle offset (0 or 4). I’ve modified the AnimateDemo routine (which gets called before MovePlayer every frame) to calculate this offset and write it directy into the MovePlayer routine:

3
4
5
6
7
8
9
10
11
12
13
14
15
; sprites.asm
 
AnimateDemo             proc
 
                        ld a, (FRAMES)                  ; Read the LSB of the ROM frame counter (0.255)
                        and %00000111                   ; Take the lowest 3 bits (effectively FRAMES modulus 8),
                        ret nz                          ;   and return 7 out of every 8 frames.
 
                        ld a, (MovePlayer.AnimOffset)   ; For every 8th frame, read player's tile offset,
                        xor %00000100                   ;       alternate between (0 => 4 => 0 => 4 => etc),
                        ld (MovePlayer.AnimOffset), a   ; SMC>  then save it back.
                        ret
pend

You should be able to see that the place it writes it into isn’t strictly speaking a piece of data; it’s the operand of a ld a, N opcode:

75
76
77
; sprites.asm
 
AnimOffset equ $+1:     add a, SMC                      ; The X offset is returned in a, add the animation offset from AnimateDemo

For this reason, the technique is usually called self-modifying code. I try and maintain the convention that code doing the modifying is marked with SMC> in the comments, and the code getting modified is marked with <SMC. The technique doesn’t work everywhere (in ROM code, for example), and it can make debugging much harder. But it’s very useful when well-controlled. I’ve defined a SMC equ 0 constant, which lets me semantically mark the code too. If I’ve written things correctly, the value always gets modified to it’s correct starting value during setup, or else having it start at zero is harmless.

The only other change I’ve made is to add the blank tile:

21
22
23
24
; database.asm
 
Blank equ ($-WTile)/Sprites.BTileLen
import_bin "..\tiles\blank.wtile"                       ; 008-008        1 x 1     Blank

This is, as you’d expect, a tile with no pixels, having black ink and paper colours. How does this so neatly erase the previous position of the player sprite? Well, NIRVANA+ sprites have an order of precedence, or z-order. Sprite A appears underneath the other sprites, and Sprite E (or H when we’re using btiles) appears on top of everything else. Sprites can partially or fully overlap, so this takes care of the messiness. It’s not a particularly good use for a scarce resource, but we’re still just exploring techniques here. When we find we’ve used all the sprites and still need more, let’s revisit this.

If you Assemble Then Emulate the part 6 code, you should be able to move our monster around the screen with the QAOP keys:

I’ve set the vertical limits (0 and 200) to let the sprite to go completely off the top and bottom of the screen. NIRVANA+ allows this because it allocates a small buffer of bytes either side of the attribute data. You can’t do the same thing with the horizontal axis, however NIRVANA (without the plus) has an 8 pixel buffer on the left and right sides. It does this at the expense of only having 30 columns of multicolour data instead of 32. This works really well for some game designs, so the choice is yours which to use. In the main, NIRVANA works pretty much the same as NIRVANA+.

If you want to keep the sprite onscreen at all times, you can adjust the vertical limits by 16:

46
47
48
49
50
51
52
53
54
55
56
57
58
; sprites.asm
 
Y equ $+1:              ld a, SMC                       ; <SMC Read the previous player vertical position
                        ld (Sprites.ALine), a           ;      and set the blank sprite vertical position to this.
                        add d                           ; Then add the vertical offset (-2/0/+2)
                        cp 14                           ; Check if we went negative
                        jp nz, MaxY                     ; If not, carry on,
                        ld a, 16                        ;   otherwise set vertical position to 0 (the minimum).
MaxY:
                        cp 184                          ; Check if we went below the bottom of the screen
                        jp c, SetY                      ; If not, carry on,
                        ld a, 184                       ;   otherwise set vertical position to 200 (the maximum).
SetY:

That’s it for part 6! Next time I will be giving some thought to the type of game we will make (the clue just might be in the name…)

ZalaXa 5 — Wide Tiles

In this tutorial I’d like to take a little step sideways, and look at something that might come in useful later, and which will pave the way to some fun stuff in part 6.

The btiles we looked at in part 4 were 16×16 pixel size. NIRVANA+ also supports 24×16 wide tiles, in a .wtile file format. Take a look at \tutorials\part5\tiles\monster.wtile in the ZX-Paintbrush graphical editor.

These are the same two monster tiles we animated in part 4, in wtile format instead of btile format, repeated four times each. We will come back to the reason for the repetitions in part 6!

In order to use these wide tiles, I’ve made a few small changes to the part 4 code. Defining the ENABLE_WIDE_SPRITE and ENABLE_WIDE_DRAW constants enables wide tiles in NIRVANA+.

40
41
42
43
; constants.asm
 
ENABLE_WIDE_SPRITE      equ true
ENABLE_WIDE_DRAW        equ true

The database has a new WTile table importing the new tiles. (I suppose it doesn’t look much like a table, but I tend to think of any data in a regular repeating format as a table—the curse of having been a database programmer for many years. In this particular case, each btile is always 48 bytes long, and a wtile is 72 bytes long, so files containing tiles are totally made up of tabular data!)

14
15
16
17
18
19
20
21
22
23
; database.asm
 
WTile proc
::WIDE_IMAGES:
;                                                           BTile         Best
;          FileName                                       Indices    Viewed As     Notes
Monster equ ($-WTile)/Sprites.BTileLen
import_bin "..\tiles\monster.wtile"                     ; 000-007        4 x 2     Preshifted monster
 
pend

You may notice that they’re in a separate table, and the WTile indices start again at 0, even though we’ve left the BTiles in the program. This is because NIRVANA+ treats them separately—in fact you can have 255 of each type of tile, and can even mix and match them.

Finally, I’ve tweaked the AnimateDemo routine to alternate the tile indices between 0 and 4. Technically-speaking, the xor opcodes do a boolean exlusive-or operation on numbers. xor %00000100 (or xor 4 in decimal) flips bit 2 of the a register, leaving the other bits unchanged. In this case, it comes to the same thing as repeatedly adding or subtracting 4 from a. Because of the cunning way our wtiles are laid out (in sets of powers of two), the effect is to select the first monster in each animation frame.

3
4
5
6
7
8
9
10
11
12
13
14
15
; sprites.asm
 
AnimateDemo             proc
 
                        ld a, (FRAMES)                  ; Read the LSB of the ROM frame counter (0.255)
                        and %00000111                   ; Take the lowest 3 bits (effectively FRAMES modulus 8),
                        ret nz                          ;   and return 7 out of every 8 frames.
 
                        ld a, (Sprites.AIndex)          ; For every 8th frame, read Sprite A's tile index,
                        xor %00000100                   ;   alternate between (0 => 4 => 0 => 4 => etc),
                        ld (Sprites.AIndex), a          ;   then save it back.
                        ret
pend

The end result looks just the same as part 4, but we have a new technique up our sleeves now!

In the next part, I’m going to make our sprite move around the screen—animation in space, as well as the animation in time we already have.