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!

  1. The digits are numbered, left to right, Score.D5 to Score.D0.

Leave a Reply

Your email address will not be published. Required fields are marked *